[arvados] created: 2.7.0-6423-gbe6311b98a

git repository hosting git at public.arvados.org
Wed Apr 24 18:34:43 UTC 2024


        at  be6311b98aea3933401df4da30b699493ba071e4 (commit)


commit be6311b98aea3933401df4da30b699493ba071e4
Author: Tom Clegg <tom at curii.com>
Date:   Mon Apr 22 10:38:33 2024 -0400

    15397: Remove code, configs, and docs for hosted git repo feature.
    
    Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom at curii.com>

diff --git a/build/rails-package-scripts/README.md b/build/rails-package-scripts/README.md
index 6ac2539f8e..08772ce02f 100644
--- a/build/rails-package-scripts/README.md
+++ b/build/rails-package-scripts/README.md
@@ -13,5 +13,4 @@ Since our build process is a tower of shell scripts, concatenating files seemed
 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).
-* 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.  Workbench doesn't set this, which causes the postinst to skip all database work.
diff --git a/build/rails-package-scripts/arvados-api-server.sh b/build/rails-package-scripts/arvados-api-server.sh
index a0e356ce32..493147d521 100644
--- a/build/rails-package-scripts/arvados-api-server.sh
+++ b/build/rails-package-scripts/arvados-api-server.sh
@@ -16,23 +16,3 @@ setup_extra_conffiles() {
   # can still be there, left over from a previous version of the API server package.
   rm -f $RELEASE_PATH/config/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 bin/rake config:dump 2>&1 | grep GitInternalDir | awk '{ print $2 }' |tr -d '"')
-      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/postinst.sh b/build/rails-package-scripts/postinst.sh
index e317f85aaf..8130f555ae 100644
--- a/build/rails-package-scripts/postinst.sh
+++ b/build/rails-package-scripts/postinst.sh
@@ -243,8 +243,6 @@ configure_version() {
 
   chown -R "$WWW_OWNER:" $RELEASE_PATH/tmp
 
-  setup_before_nginx_restart
-
   if [ -n "$SERVICE_MANAGER" ]; then
       service_command "$SERVICE_MANAGER" restart "$WEB_SERVICE"
   fi
diff --git a/build/rails-package-scripts/step2.sh b/build/rails-package-scripts/step2.sh
index 41c9cd71e3..4af0e4a01b 100644
--- a/build/rails-package-scripts/step2.sh
+++ b/build/rails-package-scripts/step2.sh
@@ -26,9 +26,6 @@ SHARED_PATH=$INSTALL_PATH/shared
 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
 
 if [ -e /run/systemd/system ]; then
     USING_SYSTEMD=1
diff --git a/build/run-build-packages-one-target.sh b/build/run-build-packages-one-target.sh
index 37fe705241..cf94ff0fcf 100755
--- a/build/run-build-packages-one-target.sh
+++ b/build/run-build-packages-one-target.sh
@@ -231,7 +231,6 @@ if test -z "$packages" ; then
         arvados-dispatch-cloud
         arvados-dispatch-lsf
         arvados-docker-cleaner
-        arvados-git-httpd
         arvados-health
         arvados-server
         arvados-src
diff --git a/build/run-build-packages.sh b/build/run-build-packages.sh
index ada3bf8b6c..661a8d9796 100755
--- a/build/run-build-packages.sh
+++ b/build/run-build-packages.sh
@@ -242,8 +242,6 @@ package_go_binary cmd/arvados-server arvados-dispatch-cloud "$FORMAT" "$ARCH" \
     "Arvados cluster cloud dispatch"
 package_go_binary cmd/arvados-server arvados-dispatch-lsf "$FORMAT" "$ARCH" \
     "Dispatch Arvados containers to an LSF cluster"
-package_go_binary cmd/arvados-server arvados-git-httpd "$FORMAT" "$ARCH" \
-    "Provide authenticated http access to Arvados-hosted git repositories"
 package_go_binary services/crunch-dispatch-local crunch-dispatch-local "$FORMAT" "$ARCH" \
     "Dispatch Crunch containers on the local system"
 package_go_binary cmd/arvados-server crunch-dispatch-slurm "$FORMAT" "$ARCH" \
diff --git a/build/run-tests.sh b/build/run-tests.sh
index 2d260c4b31..62dbed6ca6 100755
--- a/build/run-tests.sh
+++ b/build/run-tests.sh
@@ -38,7 +38,7 @@ services/api_test="TEST=test/functional/arvados/v1/collections_controller_test.r
                Restrict apiserver tests to the given file
 sdk/python_test="--test-suite tests.test_keep_locator"
                Restrict Python SDK tests to the given class
-services/githttpd_test="-check.vv"
+lib/dispatchcloud_test="-check.vv"
                Show all log messages, even when tests pass (also works
                with services/keepstore_test etc.)
 ARVADOS_DEBUG=1
@@ -85,7 +85,6 @@ lib/mount
 lib/pam
 lib/service
 services/api
-services/githttpd
 services/dockercleaner
 services/fuse
 services/fuse:py3
@@ -219,9 +218,6 @@ sanity_checks() {
     echo -n 'nginx: '
     PATH="$PATH:/sbin:/usr/sbin:/usr/local/sbin" nginx -v \
         || fatal "No nginx. Try: apt-get install nginx"
-    echo -n 'gitolite: '
-    which gitolite \
-        || fatal "No gitolite. Try: apt-get install gitolite3"
     echo -n 'npm: '
     npm --version \
         || fatal "No npm. Try: wget -O- https://nodejs.org/dist/v12.22.12/node-v12.22.12-linux-x64.tar.xz | sudo tar -C /usr/local -xJf - && sudo ln -s ../node-v12.22.12-linux-x64/bin/{node,npm} /usr/local/bin/"
@@ -393,7 +389,7 @@ start_services() {
         return 0
     fi
     . "$VENV3DIR/bin/activate"
-    echo 'Starting API, controller, keepproxy, keep-web, githttpd, ws, and nginx ssl proxy...'
+    echo 'Starting API, controller, keepproxy, keep-web, ws, and nginx ssl proxy...'
     if [[ ! -d "$WORKSPACE/services/api/log" ]]; then
         mkdir -p "$WORKSPACE/services/api/log"
     fi
@@ -421,9 +417,6 @@ start_services() {
         && python3 sdk/python/tests/run_test_server.py start_keep-web \
         && checkpidfile keep-web \
         && checkhealth WebDAV \
-        && python3 sdk/python/tests/run_test_server.py start_githttpd \
-        && checkpidfile githttpd \
-        && checkhealth GitHTTP \
         && python3 sdk/python/tests/run_test_server.py start_ws \
         && checkpidfile ws \
         && export ARVADOS_TEST_PROXY_SERVICES=1 \
@@ -444,7 +437,6 @@ stop_services() {
     . "$VENV3DIR/bin/activate" || return
     cd "$WORKSPACE" \
         && python3 sdk/python/tests/run_test_server.py stop_nginx \
-        && python3 sdk/python/tests/run_test_server.py stop_githttpd \
         && python3 sdk/python/tests/run_test_server.py stop_ws \
         && python3 sdk/python/tests/run_test_server.py stop_keep-web \
         && python3 sdk/python/tests/run_test_server.py stop_keep_proxy \
diff --git a/cmd/arvados-server/arvados-git-httpd.service b/cmd/arvados-server/arvados-git-httpd.service
deleted file mode 100644
index 517a75c03d..0000000000
--- a/cmd/arvados-server/arvados-git-httpd.service
+++ /dev/null
@@ -1,23 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-[Unit]
-Description=Arvados git server
-Documentation=https://doc.arvados.org/
-After=network.target
-AssertPathExists=/etc/arvados/config.yml
-StartLimitIntervalSec=0
-
-[Service]
-Type=notify
-EnvironmentFile=-/etc/arvados/environment
-ExecStart=/usr/bin/arvados-git-httpd
-# Set a reasonable default for the open file limit
-LimitNOFILE=65536
-Restart=always
-RestartSec=1
-RestartPreventExitStatus=2
-
-[Install]
-WantedBy=multi-user.target
diff --git a/cmd/arvados-server/cmd.go b/cmd/arvados-server/cmd.go
index c02b8fb57c..0f267a9b40 100644
--- a/cmd/arvados-server/cmd.go
+++ b/cmd/arvados-server/cmd.go
@@ -30,7 +30,6 @@ import (
 	"git.arvados.org/arvados.git/sdk/go/arvados"
 	"git.arvados.org/arvados.git/sdk/go/health"
 	dispatchslurm "git.arvados.org/arvados.git/services/crunch-dispatch-slurm"
-	"git.arvados.org/arvados.git/services/githttpd"
 	keepbalance "git.arvados.org/arvados.git/services/keep-balance"
 	keepweb "git.arvados.org/arvados.git/services/keep-web"
 	"git.arvados.org/arvados.git/services/keepproxy"
@@ -57,7 +56,6 @@ var (
 		"dispatch-cloud":     dispatchcloud.Command,
 		"dispatch-lsf":       lsf.DispatchCommand,
 		"dispatch-slurm":     dispatchslurm.Command,
-		"git-httpd":          githttpd.Command,
 		"health":             healthCommand,
 		"install":            install.Command,
 		"init":               install.InitCommand,
diff --git a/doc/_config.yml b/doc/_config.yml
index 053922a24a..d64bc4b7dc 100644
--- a/doc/_config.yml
+++ b/doc/_config.yml
@@ -141,7 +141,6 @@ navbar:
       - api/methods/pipeline_instances.html.textile.liquid
       - api/methods/pipeline_templates.html.textile.liquid
       - api/methods/nodes.html.textile.liquid
-      - api/methods/repositories.html.textile.liquid
       - api/methods/keep_disks.html.textile.liquid
     - Metadata for bioinformatics (legacy):
       - api/methods/humans.html.textile.liquid
@@ -243,13 +242,9 @@ navbar:
       - install/install-ws.html.textile.liquid
       - install/install-workbench2-app.html.textile.liquid
       - install/workbench.html.textile.liquid
-#      - install/install-composer.html.textile.liquid
     - Additional services:
       - install/install-shell-server.html.textile.liquid
       - install/install-webshell.html.textile.liquid
-      - install/install-arv-git-httpd.html.textile.liquid
-#    - Containers API (all):
-#      - install/install-jobs-image.html.textile.liquid
     - Containers API (cloud):
       - install/crunch2-cloud/install-compute-node.html.textile.liquid
       - install/crunch2-cloud/install-dispatch-cloud.html.textile.liquid
diff --git a/doc/_includes/_mount_types.liquid b/doc/_includes/_mount_types.liquid
index 86e05be866..f22f7d3551 100644
--- a/doc/_includes/_mount_types.liquid
+++ b/doc/_includes/_mount_types.liquid
@@ -24,15 +24,6 @@ At container startup, the target path will have the same directory structure as
  "kind":"collection",
  "uuid":"..."
 }</code></pre>|
-|Git tree|@git_tree@|@"uuid"@ must be the UUID of an Arvados-hosted git repository.
-@"commit"@ must be a full 40-character commit hash.
-@"path"@, if provided, must be "/".
-At container startup, the target path will have the source tree indicated by the given commit. The @.git@ metadata directory _will not_ be available.|<pre><code>{
- "kind":"git_tree",
- "uuid":"zzzzz-s0uqq-xxxxxxxxxxxxxxx",
- "commit":"f315c59f90934cccae6381e72bba59d27ba42099"
-}
-</code></pre>|
 |Temporary directory|@tmp@|@"capacity"@: capacity (in bytes) of the storage device.
 @"device_type"@ (optional, default "network"): one of @{"ram", "ssd", "disk", "network"}@ indicating the acceptable level of performance. (*note: not yet implemented as of v1.5*)
 At container startup, the target path will be empty. When the container finishes, the content will be discarded. This will be backed by a storage mechanism no slower than the specified type.|<pre><code>{
diff --git a/doc/_includes/_ssh_intro.liquid b/doc/_includes/_ssh_intro.liquid
index 8cb09f135f..4bf72c1b31 100644
--- a/doc/_includes/_ssh_intro.liquid
+++ b/doc/_includes/_ssh_intro.liquid
@@ -5,7 +5,7 @@ SPDX-License-Identifier: CC-BY-SA-3.0
 {% endcomment %}
 
 
-Arvados requires a public SSH key in order to securely log in to an Arvados VM instance, or to access an Arvados Git repository. The three sections below help you get started:
+Arvados requires a public SSH key in order to securely log in to an Arvados VM instance. The three sections below help you get started:
 
 # "Getting your SSH key":#gettingkey
 # "Adding your key to Arvados Workbench":#workbench
diff --git a/doc/_includes/_tutorial_git_repo_expectations.liquid b/doc/_includes/_tutorial_git_repo_expectations.liquid
deleted file mode 100644
index 8a172de283..0000000000
--- a/doc/_includes/_tutorial_git_repo_expectations.liquid
+++ /dev/null
@@ -1,9 +0,0 @@
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-{% include 'notebox_begin' %}
-This tutorial assumes that you have a working Arvados repository. If you do not have a repository created, you can follow the instructions in the "Adding a new repository":{{site.baseurl}}/user/tutorials/add-new-repository.html page. We will use the *$USER/tutorial* repository created in that page as the example.
-{% include 'notebox_end' %}
diff --git a/doc/admin/config-urls.html.textile.liquid b/doc/admin/config-urls.html.textile.liquid
index 3cf6e79722..1b792e9294 100644
--- a/doc/admin/config-urls.html.textile.liquid
+++ b/doc/admin/config-urls.html.textile.liquid
@@ -31,7 +31,6 @@ table(table table-bordered table-condensed).
 |controller     |yes                    |yes|yes ^2,4^|InternalURLs used by reverse proxy and container shell connections|
 |arvados-dispatch-cloud|no              |yes|no ^3^|InternalURLs only used to expose Prometheus metrics|
 |arvados-dispatch-lsf|no                |yes|no ^3^|InternalURLs only used to expose Prometheus metrics|
-|git-http       |yes                    |yes|no ^2^|InternalURLs only used by reverse proxy (e.g. Nginx)|
 |git-ssh        |yes                    |no |no    ||
 |keepproxy      |yes                    |yes|no ^2^|InternalURLs only used by reverse proxy (e.g. Nginx)|
 |keepstore      |no                     |yes|yes   |All clients connect to InternalURLs|
diff --git a/doc/admin/inspect.html.textile.liquid b/doc/admin/inspect.html.textile.liquid
index fff94cb55f..601d26c5cb 100644
--- a/doc/admin/inspect.html.textile.liquid
+++ b/doc/admin/inspect.html.textile.liquid
@@ -25,7 +25,6 @@ table(table table-bordered table-condensed table-hover){width:40em}.
 |arvados-controller|✓|
 |arvados-dispatch-cloud|✓|
 |arvados-dispatch-lsf|✓|
-|arvados-git-httpd||
 |arvados-ws|✓|
 |composer||
 |keepproxy|✓|
diff --git a/doc/admin/management-token.html.textile.liquid b/doc/admin/management-token.html.textile.liquid
index a4939b740c..5650c5038d 100644
--- a/doc/admin/management-token.html.textile.liquid
+++ b/doc/admin/management-token.html.textile.liquid
@@ -21,7 +21,6 @@ h2. API server and other services
 The following services also support monitoring.
 
 * API server
-* arvados-git-httpd
 * controller
 * keep-balance
 * keepproxy
diff --git a/doc/admin/metrics.html.textile.liquid b/doc/admin/metrics.html.textile.liquid
index ed9fbbd7ae..113536ff58 100644
--- a/doc/admin/metrics.html.textile.liquid
+++ b/doc/admin/metrics.html.textile.liquid
@@ -35,9 +35,7 @@ table(table table-bordered table-condensed table-hover).
 |arvados-controller|✓|
 |arvados-dispatch-cloud|✓|
 |arvados-dispatch-lsf|✓|
-|arvados-git-httpd||
 |arvados-ws|✓|
-|composer||
 |keepproxy|✓|
 |keepstore|✓|
 |keep-balance|✓|
diff --git a/doc/admin/user-management-cli.html.textile.liquid b/doc/admin/user-management-cli.html.textile.liquid
index c2d4743ddf..dea705ddaa 100644
--- a/doc/admin/user-management-cli.html.textile.liquid
+++ b/doc/admin/user-management-cli.html.textile.liquid
@@ -144,23 +144,3 @@ read -rd $'\000' newlink <<EOF; arv link create --link "$newlink"
 }
 EOF
 </pre>
-
-h3. Git repository
-
-Give @$user_uuid@ permission to commit to @$repo_uuid@ as @$repo_username@
-
-<pre>
-user_uuid=xxxxxxxchangeme
-repo_uuid=xxxxxxxchangeme
-repo_username=xxxxxxxchangeme
-
-read -rd $'\000' newlink <<EOF; arv link create --link "$newlink"
-{
-"tail_uuid":"$user_uuid",
-"head_uuid":"$repo_uuid",
-"link_class":"permission",
-"name":"can_write",
-"properties":{"username":"$repo_username"}
-}
-EOF
-</pre>
diff --git a/doc/admin/user-management.html.textile.liquid b/doc/admin/user-management.html.textile.liquid
index 7d30ee88d1..994081901c 100644
--- a/doc/admin/user-management.html.textile.liquid
+++ b/doc/admin/user-management.html.textile.liquid
@@ -60,7 +60,6 @@ notextile. <div class="spaced-out">
 # A new user record is not set up, and not active.  An inactive user cannot create or update any object, but can read Arvados objects that the user account has permission to read (such as publicly available items readable by the "anonymous" user).
 # Using Workbench or the "command line":{{site.baseurl}}/admin/user-management-cli.html , the admin invokes @setup@ on the user.  The setup method adds the user to the "All users" group.
 - If "Users.AutoSetupNewUsers":config.html is true, this happens automatically during user creation, so in that case new users start at step (3).
-- If "Users.AutoSetupNewUsersWithRepository":config.html is true, a new git repo is created for the user.
 - If "Users.AutoSetupNewUsersWithVmUUID":config.html is set, the user is given login permission to the specified shell node
 # User is set up, but still not yet active.  The browser presents "user agreements":#user_agreements (if any) and then invokes the user @activate@ method on the user's behalf.
 # The user @activate@ method checks that all "user agreements":#user_agreements are signed.  If so, or there are no user agreements, the user is activated.
diff --git a/doc/architecture/index.html.textile.liquid b/doc/architecture/index.html.textile.liquid
index f5405c16e1..230a9be8f5 100644
--- a/doc/architecture/index.html.textile.liquid
+++ b/doc/architecture/index.html.textile.liquid
@@ -19,7 +19,6 @@ Located in @arvados/services at .
 table(table table-bordered table-condensed).
 |_. Component|_. Description|
 |api|The API server is the core of Arvados.  It is backed by a Postgres database and manages information such as metadata for storage, a record of submitted compute jobs, users, groups, and associated permissions.|
-|arvados-git-httpd|Provides a git+http interface to Arvados-managed git repositories, with permissions and authentication based on an Arvados API token.|
 |arvados-dispatch-cloud|Provide elastic computing by creating and destroying cloud based virtual machines on compute demand.|
 |crunch-dispatch-local|Get compute requests submitted to the API server and execute them locally.|
 |crunch-dispatch-slurm|Get compute requests submitted to the API server and submit them to slurm.|
diff --git a/doc/images/add-new-repository.png b/doc/images/add-new-repository.png
deleted file mode 100644
index d62a9869a2..0000000000
Binary files a/doc/images/add-new-repository.png and /dev/null differ
diff --git a/doc/install/arvbox.html.textile.liquid b/doc/install/arvbox.html.textile.liquid
index 20e1c48eee..7265e2d5cc 100644
--- a/doc/install/arvbox.html.textile.liquid
+++ b/doc/install/arvbox.html.textile.liquid
@@ -28,7 +28,7 @@ $ <span class="userinput">./arvbox start localdemo</span>
 
 Arvados-in-a-box starting
 
-Waiting for workbench2 websockets workbench webshell keep-web controller keepproxy api keepstore1 arv-git-httpd keepstore0 sdk vm ...
+Waiting for workbench2 websockets workbench webshell keep-web controller keepproxy api keepstore1 keepstore0 sdk vm ...
 
 ...
 
diff --git a/doc/install/install-arv-git-httpd.html.textile.liquid b/doc/install/install-arv-git-httpd.html.textile.liquid
deleted file mode 100644
index 476c89005f..0000000000
--- a/doc/install/install-arv-git-httpd.html.textile.liquid
+++ /dev/null
@@ -1,298 +0,0 @@
----
-layout: default
-navsection: installguide
-title: Install the Git server
-...
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-# "Introduction":#introduction
-# "Install dependencies":#dependencies
-# "Create "git" user and storage directory":#create
-# "Install gitolite":#gitolite
-# "Configure gitolite":#config-gitolite
-# "Configure git synchronization":#sync
-# "Update config.yml":#update-config
-# "Update nginx configuration":#update-nginx
-# "Install arvados-git-httpd package":#install-packages
-# "Restart the API server and controller":#restart-api
-# "Confirm working installation":#confirm-working
-
-h2(#introduction). Introduction
-
-Arvados support for git repository management enables using Arvados permissions to control access to git repositories.  Users can create their own private and public git repositories and share them with others.
-
-The git hosting setup involves three components.
-* The "arvados-git-sync.rb" script polls the API server for the current list of repositories, creates bare repositories, and updates the local permission cache used by gitolite.
-* Gitolite provides SSH access.  Users authenticate by SSH keys.
-* arvados-git-http provides HTTPS access.  Users authenticate by Arvados tokens.
-
-Git services must be installed on the same host as the Arvados Rails API server.
-
-h2(#dependencies). Install dependencies
-
-h3. Alma/CentOS/Red Hat/Rocky
-
-<notextile>
-<pre><code># <span class="userinput">dnf install git perl-Data-Dumper openssh-server</span>
-</code></pre>
-</notextile>
-
-h3. Debian and Ubuntu
-
-<notextile>
-<pre><code># <span class="userinput">apt-get --no-install-recommends install git openssh-server</span>
-</code></pre>
-</notextile>
-
-h2(#create). Create "git" user and storage directory
-
-Gitolite and some additional scripts will be installed in @/var/lib/arvados/git@, which means hosted repository data will be stored in @/var/lib/arvados/git/repositories at . If you choose to install gitolite in a different location, make sure to update the @git_repositories_dir@ entry in your API server's @application.yml@ file accordingly: for example, if you install gitolite at @/data/gitolite@ then your @git_repositories_dir@ will be @/data/gitolite/repositories at .
-
-A new UNIX account called "git" will own the files. This makes git URLs look familiar to users (<code>git@[...]:username/reponame.git</code>).
-
-On Debian- or Red Hat-based systems:
-
-<notextile>
-<pre><code>gitserver:~$ <span class="userinput">sudo mkdir -p /var/lib/arvados/git</span>
-gitserver:~$ <span class="userinput">sudo useradd --comment git --home-dir /var/lib/arvados/git git</span>
-gitserver:~$ <span class="userinput">sudo chown -R git:git ~git</span>
-</code></pre>
-</notextile>
-
-The git user needs its own SSH key. (It must be able to run <code>ssh git at localhost</code> from scripts.)
-
-<notextile>
-<pre><code>gitserver:~$ <span class="userinput">sudo -u git -i bash</span>
-git at gitserver:~$ <span class="userinput">ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa</span>
-git at gitserver:~$ <span class="userinput">cp .ssh/id_rsa.pub .ssh/authorized_keys</span>
-git at gitserver:~$ <span class="userinput">ssh -o stricthostkeychecking=no localhost cat .ssh/id_rsa.pub</span>
-Warning: Permanently added 'localhost' (ECDSA) to the list of known hosts.
-ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7aBIDAAgMQN16Pg6eHmvc+D+6TljwCGr4YGUBphSdVb25UyBCeAEgzqRiqy0IjQR2BLtSirXr+1SJAcQfBgI/jwR7FG+YIzJ4ND9JFEfcpq20FvWnMMQ6XD3y3xrZ1/h/RdBNwy4QCqjiXuxDpDB7VNP9/oeAzoATPZGhqjPfNS+RRVEQpC6BzZdsR+S838E53URguBOf9yrPwdHvosZn7VC0akeWQerHqaBIpSfDMtaM4+9s1Gdsz0iP85rtj/6U/K/XOuv2CZsuVZZ52nu3soHnEX2nx2IaXMS3L8Z+lfOXB2T6EaJgXF7Z9ME5K1tx9TSNTRcYCiKztXLNLSbp git at gitserver
-git at gitserver:~$ <span class="userinput">rm .ssh/authorized_keys</span>
-</code></pre>
-</notextile>
-
-h2(#gitolite). Install gitolite
-
-Check "https://github.com/sitaramc/gitolite/tags":https://github.com/sitaramc/gitolite/tags for the latest stable version. This guide was tested with @v3.6.11 at . _Versions below 3.0 are missing some features needed by Arvados, and should not be used._
-
-Download and install the version you selected.
-
-<notextile>
-<pre><code>$ <span class="userinput">sudo -u git -i bash</span>
-git at gitserver:~$ <span class="userinput">echo 'PATH=$HOME/bin:$PATH' >.profile</span>
-git at gitserver:~$ <span class="userinput">. .profile</span>
-git at gitserver:~$ <span class="userinput">git clone --branch <b>v3.6.11</b> https://github.com/sitaramc/gitolite</span>
-...
-Note: checking out '5d24ae666bfd2fa9093d67c840eb8d686992083f'.
-...
-git at gitserver:~$ <span class="userinput">mkdir bin</span>
-git at gitserver:~$ <span class="userinput">gitolite/install -ln ~git/bin</span>
-git at gitserver:~$ <span class="userinput">bin/gitolite setup -pk .ssh/id_rsa.pub</span>
-Initialized empty Git repository in /var/lib/arvados/git/repositories/gitolite-admin.git/
-Initialized empty Git repository in /var/lib/arvados/git/repositories/testing.git/
-WARNING: /var/lib/arvados/git/.ssh/authorized_keys missing; creating a new one
-    (this is normal on a brand new install)
-</code></pre>
-</notextile>
-
-_If this didn't go well, more detail about installing gitolite, and information about how it works, can be found on the "gitolite home page":http://gitolite.com/._
-
-Clone the gitolite-admin repository. The arvados-git-sync.rb script works by editing the files in this working directory and pushing them to gitolite. Here we make sure "git push" won't produce any errors or warnings.
-
-<notextile>
-<pre><code>git at gitserver:~$ <span class="userinput">git clone git at localhost:gitolite-admin</span>
-Cloning into 'gitolite-admin'...
-remote: Counting objects: 6, done.
-remote: Compressing objects: 100% (4/4), done.
-remote: Total 6 (delta 0), reused 0 (delta 0)
-Receiving objects: 100% (6/6), done.
-Checking connectivity... done.
-git at gitserver:~$ <span class="userinput">cd gitolite-admin</span>
-git at gitserver:~/gitolite-admin$ <span class="userinput">git config user.email arvados</span>
-git at gitserver:~/gitolite-admin$ <span class="userinput">git config user.name arvados</span>
-git at gitserver:~/gitolite-admin$ <span class="userinput">git config push.default simple</span>
-git at gitserver:~/gitolite-admin$ <span class="userinput">git push</span>
-Everything up-to-date
-</code></pre>
-</notextile>
-
-h2(#config-gitolite). Configure gitolite
-
-Configure gitolite to look up a repository name like @username/reponame.git@ and find the appropriate bare repository storage directory.
-
-Add the following lines to the top of @~git/.gitolite.rc@:
-
-<notextile>
-<pre><code><span class="userinput">my $repo_aliases;
-my $aliases_src = "$ENV{HOME}/.gitolite/arvadosaliases.pl";
-if ($ENV{HOME} && (-e $aliases_src)) {
-    $repo_aliases = do $aliases_src;
-}
-$repo_aliases ||= {};
-</span></code></pre>
-</notextile>
-
-Add the following lines inside the section that begins @%RC = (@:
-
-<notextile>
-<pre><code><span class="userinput">    REPO_ALIASES => $repo_aliases,
-</span></code></pre>
-</notextile>
-
-Inside that section, adjust the 'UMASK' setting to @022@, to ensure the API server has permission to read repositories:
-
-<notextile>
-<pre><code>    UMASK => <span class="userinput">022</span>,
-</code></pre>
-</notextile>
-
-Uncomment the 'Alias' line in the section that begins @ENABLE => [@:
-
-<notextile>
-<pre><code><span class="userinput">            # access a repo by another (possibly legacy) name
-            'Alias',
-</span></code></pre>
-</notextile>
-
-h2(#sync). Configure git synchronization
-
-Create a configuration file @/var/www/arvados-api/current/config/arvados-clients.yml@ using the following template, filling in the appropriate values for your system.
-* For @arvados_api_token@, use @SystemRootToken@
-* For @gitolite_arvados_git_user_key@, provide the public key you generated above, i.e., the contents of @~git/.ssh/id_rsa.pub at .
-
-<notextile>
-<pre><code>production:
-  gitolite_url: /var/lib/arvados/git/repositories/gitolite-admin.git
-  gitolite_tmp: /var/lib/arvados/git
-  arvados_api_host: <span class="userinput">ClusterID.example.com</span>
-  arvados_api_token: "<span class="userinput">zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz</span>"
-  arvados_api_host_insecure: <span class="userinput">false</span>
-  gitolite_arvados_git_user_key: "<span class="userinput">ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7aBIDAAgMQN16Pg6eHmvc+D+6TljwCGr4YGUBphSdVb25UyBCeAEgzqRiqy0IjQR2BLtSirXr+1SJAcQfBgI/jwR7FG+YIzJ4ND9JFEfcpq20FvWnMMQ6XD3y3xrZ1/h/RdBNwy4QCqjiXuxDpDB7VNP9/oeAzoATPZGhqjPfNS+RRVEQpC6BzZdsR+S838E53URguBOf9yrPwdHvosZn7VC0akeWQerHqaBIpSfDMtaM4+9s1Gdsz0iP85rtj/6U/K/XOuv2CZsuVZZ52nu3soHnEX2nx2IaXMS3L8Z+lfOXB2T6EaJgXF7Z9ME5K1tx9TSNTRcYCiKztXLNLSbp git at gitserver</span>"
-</code></pre>
-</notextile>
-
-<pre>
-$ sudo chown git:git /var/www/arvados-api/current/config/arvados-clients.yml
-$ sudo chmod og-rwx /var/www/arvados-api/current/config/arvados-clients.yml
-</pre>
-
-h3. Test configuration
-
-notextile. <pre><code>$ <span class="userinput">sudo -u git -i bash -c 'cd /var/www/arvados-api/current && bin/bundle exec script/arvados-git-sync.rb production'</span></code></pre>
-
-h3. Enable the synchronization script
-
-The API server package includes a script that retrieves the current set of repository names and permissions from the API, writes them to @arvadosaliases.pl@ in a format usable by gitolite, and triggers gitolite hooks which create new empty repositories if needed. This script should run every 2 to 5 minutes.
-
-Create @/etc/cron.d/arvados-git-sync@ with the following content:
-
-<notextile>
-<pre><code><span class="userinput">*/5 * * * * git cd /var/www/arvados-api/current && bin/bundle exec script/arvados-git-sync.rb production</span>
-</code></pre>
-</notextile>
-
-h2(#update-config). Update config.yml
-
-Edit the cluster config at @config.yml@ .
-
-<notextile>
-<pre><code>    Services:
-      GitSSH:
-        ExternalURL: "<span class="userinput">ssh://git@git.ClusterID.example.com</span>"
-      GitHTTP:
-        ExternalURL: <span class="userinput">https://git.ClusterID.example.com/</span>
-        InternalURLs:
-	  "http://localhost:9001": {}
-    Git:
-      GitCommand: <span class="userinput">/var/lib/arvados/git/gitolite/src/gitolite-shell</span>
-      GitoliteHome: <span class="userinput">/var/lib/arvados/git</span>
-      Repositories: <span class="userinput">/var/lib/arvados/git/repositories</span>
-</code></pre>
-</notextile>
-
-h2(#update-nginx). Update nginx configuration
-
-Use a text editor to create a new file @/etc/nginx/conf.d/arvados-git.conf@ with the following configuration.  Options that need attention are marked in <span class="userinput">red</span>.
-
-<notextile>
-<pre><code>upstream arvados-git-httpd {
-  server                  127.0.0.1:<span class="userinput">9001</span>;
-}
-server {
-  listen                  443 ssl;
-  server_name             git.<span class="userinput">ClusterID.example.com</span>;
-  proxy_connect_timeout   90s;
-  proxy_read_timeout      300s;
-
-  ssl_certificate         <span class="userinput">/YOUR/PATH/TO/cert.pem</span>;
-  ssl_certificate_key     <span class="userinput">/YOUR/PATH/TO/cert.key</span>;
-
-  # The server needs to accept potentially large refpacks from push clients.
-  client_max_body_size 128m;
-
-  location  / {
-    proxy_pass            http://arvados-git-httpd;
-  }
-}
-</code></pre>
-</notextile>
-
-h2(#install-packages). Install the arvados-git-httpd package
-
-The arvados-git-httpd package provides HTTP access, using Arvados authentication tokens instead of passwords. It must be installed on the system where your git repositories are stored.
-
-h3. Alma/CentOS/Red Hat/Rocky
-
-<notextile>
-<pre><code># <span class="userinput">dnf install arvados-git-httpd</span>
-</code></pre>
-</notextile>
-
-h3. Debian and Ubuntu
-
-<notextile>
-<pre><code># <span class="userinput">apt-get --no-install-recommends install arvados-git-httpd</span>
-</code></pre>
-</notextile>
-
-h2(#restart-api). Restart the API server and controller
-
-After adding Workbench to the Services section, make sure the cluster config file is up to date on the API server host, and restart the API server and controller processes to ensure the changes are applied.
-
-<notextile>
-<pre><code># <span class="userinput">systemctl restart nginx arvados-controller</span>
-</code></pre>
-</notextile>
-
-h2(#confirm-working). Confirm working installation
-
-Create 'testrepo' in the Arvados database.
-
-<notextile>
-<pre><code>~$ <span class="userinput">arv --format=uuid repository create --repository '{"name":"myusername/testrepo"}'</span>
-</code></pre></notextile>
-
-The arvados-git-sync cron job will notice the new repository record and create a repository on disk.  Because it is on a timer (default 5 minutes) you may have to wait a minute or two for it to show up.
-
-h3. SSH
-
-Before you do this, go to Workbench and choose *SSH Keys* from the menu, and upload your public key.  Arvados uses the public key to identify you when you access the git repo.
-
-<notextile>
-<pre><code>~$ <span class="userinput">git clone git at git.ClusterID.example.com:username/testrepo.git</span>
-</code></pre>
-</notextile>
-
-h3. HTTP
-
-Set up git credential helpers as described in "install shell server":install-shell-server.html#config-git for the git command to use your API token instead of prompting you for a username and password.
-
-<notextile>
-<pre><code>~$ <span class="userinput">git clone https://git.ClusterID.example.com/username/testrepo.git</span>
-</code></pre>
-</notextile>
diff --git a/doc/install/install-composer.html.textile.liquid b/doc/install/install-composer.html.textile.liquid
deleted file mode 100644
index 58ba5d03a0..0000000000
--- a/doc/install/install-composer.html.textile.liquid
+++ /dev/null
@@ -1,65 +0,0 @@
----
-layout: default
-navsection: installguide
-title: Install Composer
-...
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-Arvados Composer is a web-based javascript application for building Common Workflow Languge (CWL) Workflows.
-
-# "Install dependencies":#dependencies
-# "Update config.yml":#update-config
-# "Update Nginx configuration":#update-nginx
-# "Install arvados-composer":#install-packages
-# "Restart the API server and controller":#restart-api
-# "Confirm working installation":#confirm-working
-
-h2(#dependencies). Install dependencies
-
-In addition to Arvados core services, Composer requires "Arvados hosted git repositories":install-arv-git-httpd.html which are used for storing workflow files.
-
-h2(#configure). Update config.yml
-
-Edit @config.yml@ and set @Services.Composer.ExternalURL@ to the location from which it is served:
-
-<notextile>
-<pre><code>    Services:
-      Composer:
-        ExternalURL: <span class="userinput">https://workbench.CusterID.example.com/composer</span></code></pre>
-</notextile>
-
-h2(#update-nginx). Update nginx configuration
-
-Composer may be served from the same host as Workbench.  Composer communicates directly with the Arvados API server.  It does not require its own backend and should be served as a static file.
-
-Add the following @location@ sections to @/etc/nginx/conf.d/arvados-workbench.conf@ .
-
-<notextile>
-<pre><code>server {
-  [...]
-
-  location /composer {
-    root   /var/www/arvados-composer;
-    index  index.html;
-  }
-
-  location /composer/composer.yml {
-    return 200 '{ "API_HOST": "<span class="userinput">ClusterID.example.com</span>" }';
-  }
-}
-</code></pre>
-</notextile>
-
-{% assign arvados_component = 'arvados-composer' %}
-
-{% include 'install_packages' %}
-
-{% include 'restart_api' %}
-
-h2(#confirm-working). Confirm working installation
-
-Visit @https://workbench.ClusterID.example.com/composer@ in a browser.  You should be able to log in using the login method you configured previously.
diff --git a/doc/install/install-jobs-image.html.textile.liquid b/doc/install/install-jobs-image.html.textile.liquid
deleted file mode 100644
index efd8c9649f..0000000000
--- a/doc/install/install-jobs-image.html.textile.liquid
+++ /dev/null
@@ -1,38 +0,0 @@
----
-layout: default
-navsection: installguide
-title: Install arvados/jobs image
-...
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-h2. Create a project for Docker images
-
-Here we create a default project for the standard Arvados Docker images, and give all users read access to it. The project is owned by the system user.
-
-<notextile>
-<pre><code>~$ <span class="userinput">uuid_prefix=$(arv --format=uuid user current | cut -d- -f1)</span>
-~$ <span class="userinput">project_uuid=$(arv --format=uuid group create --group '{"owner_uuid":"'$uuid_prefix'-tpzed-000000000000000", "group_class":"project", "name":"Arvados Standard Docker Images"}')</span>
-~$ <span class="userinput">echo "Arvados project uuid is '$project_uuid'"</span>
-~$ <span class="userinput">read -rd $'\000' newlink <<EOF; arv link create --link "$newlink"</span>
-<span class="userinput">{
- "tail_uuid":"${uuid_prefix}-j7d0g-fffffffffffffff",
- "head_uuid":"$project_uuid",
- "link_class":"permission",
- "name":"can_read"
-}
-EOF</span>
-</code></pre></notextile>
-
-h2. Import the arvados/jobs docker image
-
-In order to start workflows from workbench, there needs to be Docker image @arvados/jobs@ tagged with the version of Arvados you are installing. The following command downloads the latest arvados/jobs image from Docker Hub, loads it into Keep.  In this example @$project_uuid@ should be the UUID of the "Arvados Standard Docker Images" project.
-
-<notextile>
-<pre><code>~$ <span class="userinput">arv-keepdocker --pull arvados/jobs latest --project-uuid $project_uuid</span>
-</code></pre></notextile>
-
-If the image needs to be downloaded from Docker Hub, the command can take a few minutes to complete, depending on available network bandwidth.
diff --git a/doc/install/install-manual-prerequisites.html.textile.liquid b/doc/install/install-manual-prerequisites.html.textile.liquid
index 8819b0210f..ba179f82dd 100644
--- a/doc/install/install-manual-prerequisites.html.textile.liquid
+++ b/doc/install/install-manual-prerequisites.html.textile.liquid
@@ -47,7 +47,6 @@ table(table table-bordered table-condensed).
 |\3=. *Additional services*|
 |"Websockets server":install-ws.html |Event distribution server.|Required to view streaming container logs in Workbench.|
 |"Shell server":install-shell-server.html |Grant Arvados users access to Unix shell accounts on dedicated shell nodes.|Optional.|
-|"Git server":install-arv-git-httpd.html |Arvados-hosted git repositories, with Arvados-token based authentication.|Optional|
 |\3=. *Crunch (running containers)*|
 |"arvados-dispatch-cloud":crunch2-cloud/install-dispatch-cloud.html |Run analysis workflows on cloud by allocating and freeing cloud VM instances on demand.|Optional|
 |"crunch-dispatch-slurm":crunch2-slurm/install-dispatch.html |Run analysis workflows distributed across a Slurm cluster.|Optional|
@@ -96,7 +95,7 @@ For a production installation, this is a reasonable starting point:
 <div class="offset1">
 table(table table-bordered table-condensed).
 |_. Function|_. Number of nodes|_. Recommended specs|
-|PostgreSQL database, Arvados API server, Arvados controller, Git, Websockets, Container dispatcher|1|16+ GiB RAM, 4+ cores, fast disk for database|
+|PostgreSQL database, Arvados API server, Arvados controller, Websockets, Container dispatcher|1|16+ GiB RAM, 4+ cores, fast disk for database|
 |Workbench, Keepproxy, Keep-web, Keep-balance|1|8 GiB RAM, 2+ cores|
 |Keepstore servers ^1^|2+|4 GiB RAM|
 |Compute worker nodes ^1^|0+ |Depends on workload; scaled dynamically in the cloud|
@@ -138,7 +137,6 @@ It is possible to use custom DNS names for the Arvados services.
 table(table table-bordered table-condensed).
 |_. Function|_. DNS name|
 |Arvados API|@ClusterID.example.com@|
-|Arvados Git server|git. at ClusterID.example.com@|
 |Arvados Webshell|webshell. at ClusterID.example.com@|
 |Arvados Websockets endpoint|ws. at ClusterID.example.com@|
 |Arvados Workbench|workbench. at ClusterID.example.com@|
diff --git a/doc/install/install-shell-server.html.textile.liquid b/doc/install/install-shell-server.html.textile.liquid
index f864f37563..9520c08397 100644
--- a/doc/install/install-shell-server.html.textile.liquid
+++ b/doc/install/install-shell-server.html.textile.liquid
@@ -12,7 +12,6 @@ SPDX-License-Identifier: CC-BY-SA-3.0
 # "Introduction":#introduction
 # "Install Dependencies and SDKs":#dependencies
 # "Install git and curl":#install-packages
-# "Update Git Config":#config-git
 # "Create record for VM":#vm-record
 # "Install arvados-login-sync":#arvados-login-sync
 # "Confirm working installation":#confirm-working
@@ -44,17 +43,6 @@ h2(#dependencies). Install Dependencies and SDKs
 
 {% include 'install_packages' %}
 
-h2(#config-git). Update Git Config
-
-Configure git to use the ARVADOS_API_TOKEN environment variable to authenticate to arvados-git-httpd. We use the @--system@ flag so it takes effect for all current and future user accounts. It does not affect git's behavior when connecting to other git servers.
-
-<notextile>
-<pre>
-<code># <span class="userinput">git config --system 'credential.https://git.<b>ClusterID.example.com</b>/.username' none</span></code>
-<code># <span class="userinput">git config --system 'credential.https://git.<b>ClusterID.example.com</b>/.helper' '!cred(){ cat >/dev/null; if [ "$1" = get ]; then echo password=$ARVADOS_API_TOKEN; fi; };cred'</span></code>
-</pre>
-</notextile>
-
 h2(#vm-record). Create record for VM
 
 As an admin, create an Arvados virtual_machine object representing this shell server. This will return a uuid.
diff --git a/doc/user/tutorials/add-new-repository.html.textile.liquid b/doc/user/tutorials/add-new-repository.html.textile.liquid
deleted file mode 100644
index 6046e7d14b..0000000000
--- a/doc/user/tutorials/add-new-repository.html.textile.liquid
+++ /dev/null
@@ -1,47 +0,0 @@
----
-layout: default
-navsection: userguide
-title: Adding a new Arvados git repository
-...
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-Arvados supports managing git repositories. You can access these repositories using your Arvados credentials and share them with other Arvados users.
-
-{% include 'tutorial_expectations' %}
-
-h2. Setting up Git
-
-Before you start using Git and arvados repositories, you should do some basic configuration (you only need to do this the first time):
-
-<notextile>
-<pre><code>~$ <span class="userinput">git config --global user.name "Your Name"</span>
-~$ <span class="userinput">git config --global user.email $USER at example.com</span></code></pre>
-</notextile>
-
-h2. Add "tutorial" repository
-
-On the Arvados Workbench, click on the dropdown menu icon <i class="fa fa-lg fa-user"></i> (Account Management) in the upper right corner of the top navigation menu to access the user settings menu, and click on the menu item *Repositories*.
-
-In the *Repositories* page, you will see the <span class="btn btn-sm btn-primary">+ NEW REPOSITORY</span> button.
-
-!{width: 100%;}{{ site.baseurl }}/images/repositories-panel.png!
-
-Click the <span class="btn btn-sm btn-primary">+ NEW REPOSITORY</span> button to open the popup to add a new Arvados repository. You will see a text box where you can enter the name of the repository. Enter *tutorial* in this text box and click on *Create*.
-
-{% include 'notebox_begin' %}
-The name you enter here must begin with a letter and can only contain alphanumeric characters.
-{% include 'notebox_end' %}
-
-!{width: 100%;}{{ site.baseurl }}/images/add-new-repository.png!
-
-This will create a new repository with the name @$USER/tutorial at . It can be accessed using the URL <notextile><code>https://git.{{ site.arvados_api_host }}/$USER/tutorial.git</code></notextile> or <notextile><code>git at git.{{ site.arvados_api_host }}:$USER/tutorial.git</code></notextile>
-
-Back in the *Repositories* page, you should see the @$USER/tutorial@ repository listed in the name column with these URLs.
-
-!{display: block;margin-left: 25px;margin-right: auto;}{{ site.baseurl }}/images/added-new-repository.png!
-
-You are now ready to use this *tutorial* repository to run your crunch scripts.
diff --git a/doc/user/tutorials/git-arvados-guide.html.textile.liquid b/doc/user/tutorials/git-arvados-guide.html.textile.liquid
deleted file mode 100644
index a4ac2a5795..0000000000
--- a/doc/user/tutorials/git-arvados-guide.html.textile.liquid
+++ /dev/null
@@ -1,87 +0,0 @@
----
-layout: default
-navsection: userguide
-title: Working with an Arvados git repository
-...
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-This tutorial describes how to work with an Arvados-managed git repository. Working with an Arvados git repository is very similar to working with other public git repositories.
-
-{% include 'tutorial_expectations' %}
-
-{% include 'tutorial_git_repo_expectations' %}
-
-h2. Cloning a git repository
-
-Before you start using Git, you should do some basic configuration (you only need to do this the first time):
-
-<notextile>
-<pre><code>~$ <span class="userinput">git config --global user.name "Your Name"</span>
-~$ <span class="userinput">git config --global user.email $USER at example.com</span></code></pre>
-</notextile>
-
-On the Arvados Workbench, click on the dropdown menu icon <i class="fa fa-lg fa-user"></i> in the upper right corner of the top navigation menu to access the Account Management menu, and click on the menu item *Repositories*. In the *Repositories* page, you should see the @$USER/tutorial@ repository listed in the *name* column.  Next to *name* is the column *URL*. Copy the *URL* value associated with your repository.  This should look like <notextile><code>https://git.{{ site.arvados_api_host }}/$USER/tutorial.git</code></notextile>. Alternatively, you can use <notextile><code>git at git.{{ site.arvados_api_host }}:$USER/tutorial.git</code></notextile>
-
-Next, on the Arvados virtual machine, clone your Git repository:
-
-<notextile>
-<pre><code>~$ <span class="userinput">cd $HOME</span> # (or wherever you want to install)
-~$ <span class="userinput">git clone https://git.{{ site.arvados_api_host }}/$USER/tutorial.git</span>
-Cloning into 'tutorial'...</code></pre>
-</notextile>
-
-This will create a Git repository in the directory called @tutorial@ in your home directory. Say yes when prompted to continue with connection.
-Ignore any warning that you are cloning an empty repository.
-
-*Note:* If you are prompted for username and password when you try to git clone using this command, you may first need to update your git configuration. Execute the following commands to update your git configuration.
-
-<notextile>
-<pre>
-<code>~$ <span class="userinput">git config 'credential.https://git.{{ site.arvados_api_host }}/.username' none</span></code>
-<code>~$ <span class="userinput">git config 'credential.https://git.{{ site.arvados_api_host }}/.helper' '!cred(){ cat >/dev/null; if [ "$1" = get ]; then echo password=$ARVADOS_API_TOKEN; fi; };cred'</span></code>
-</pre>
-</notextile>
-
-h2. Creating a git branch in an Arvados repository
-
-Create a git branch named *tutorial_branch* in the *tutorial* Arvados git repository.
-
-<notextile>
-<pre><code>~$ <span class="userinput">cd tutorial</span>
-~/tutorial$ <span class="userinput">git checkout -b tutorial_branch</span>
-</code></pre>
-</notextile>
-
-h2. Adding scripts to an Arvados repository
-
-A git repository is a good place to store the CWL workflows that you run on Arvados.
-
-First, create a simple CWL CommandLineTool:
-
-notextile. <pre>~/tutorials$ <code class="userinput">nano hello.cwl</code></pre>
-
-<notextile> {% code tutorial_hello_cwl as yaml %} </notextile>
-
-Next, add the file to the git repository.  This tells @git@ that the file should be included on the next commit.
-
-notextile. <pre><code>~/tutorial$ <span class="userinput">git add hello.cwl</span></code></pre>
-
-Next, commit your changes.  All staged changes are recorded into the local git repository:
-
-<notextile>
-<pre><code>~/tutorial$ <span class="userinput">git commit -m "my first script"</span>
-</code></pre>
-</notextile>
-
-Finally, upload your changes to the remote repository:
-
-<notextile>
-<pre><code>~/tutorial/crunch_scripts$ <span class="userinput">git push origin tutorial_branch</span>
-</code></pre>
-</notextile>
-
-The same steps can be used to add any of your custom bash, R, or python scripts to an Arvados repository.
diff --git a/lib/boot/nginx.go b/lib/boot/nginx.go
index 22f1e665fa..338a6b5bcc 100644
--- a/lib/boot/nginx.go
+++ b/lib/boot/nginx.go
@@ -74,7 +74,6 @@ func (runNginx) Run(ctx context.Context, fail func(error), super *Supervisor) er
 		{"KEEPWEB", super.cluster.Services.WebDAV},
 		{"KEEPWEBDL", super.cluster.Services.WebDAVDownload},
 		{"KEEPPROXY", super.cluster.Services.Keepproxy},
-		{"GIT", super.cluster.Services.GitHTTP},
 		{"HEALTH", super.cluster.Services.Health},
 		{"WORKBENCH1", super.cluster.Services.Workbench1},
 		{"WORKBENCH2", super.cluster.Services.Workbench2},
diff --git a/lib/boot/supervisor.go b/lib/boot/supervisor.go
index ac269b933a..67649e75de 100644
--- a/lib/boot/supervisor.go
+++ b/lib/boot/supervisor.go
@@ -366,7 +366,6 @@ func (super *Supervisor) runCluster() error {
 		runNginx{},
 		railsDatabase{},
 		runServiceCommand{name: "controller", svc: super.cluster.Services.Controller, depends: []supervisedTask{railsDatabase{}}},
-		runServiceCommand{name: "git-httpd", svc: super.cluster.Services.GitHTTP},
 		runServiceCommand{name: "health", svc: super.cluster.Services.Health},
 		runServiceCommand{name: "keepproxy", svc: super.cluster.Services.Keepproxy, depends: []supervisedTask{runPassenger{src: "services/api"}}},
 		runServiceCommand{name: "keepstore", svc: super.cluster.Services.Keepstore},
@@ -821,7 +820,6 @@ func (super *Supervisor) autofillConfig() error {
 	for _, svc := range []*arvados.Service{
 		&super.cluster.Services.Controller,
 		&super.cluster.Services.DispatchCloud,
-		&super.cluster.Services.GitHTTP,
 		&super.cluster.Services.Health,
 		&super.cluster.Services.Keepproxy,
 		&super.cluster.Services.Keepstore,
@@ -839,7 +837,6 @@ func (super *Supervisor) autofillConfig() error {
 			}
 			host := net.JoinHostPort(defaultExtHost, port)
 			if svc == &super.cluster.Services.Controller ||
-				svc == &super.cluster.Services.GitHTTP ||
 				svc == &super.cluster.Services.Health ||
 				svc == &super.cluster.Services.Keepproxy ||
 				svc == &super.cluster.Services.WebDAV ||
diff --git a/lib/config/config.default.yml b/lib/config/config.default.yml
index 434ffceac0..cb5568aef8 100644
--- a/lib/config/config.default.yml
+++ b/lib/config/config.default.yml
@@ -74,12 +74,6 @@ Clusters:
       Keepbalance:
         InternalURLs: {SAMPLE: {ListenURL: ""}}
         ExternalURL: ""
-      GitHTTP:
-        InternalURLs: {SAMPLE: {ListenURL: ""}}
-        ExternalURL: ""
-      GitSSH:
-        InternalURLs: {SAMPLE: {ListenURL: ""}}
-        ExternalURL: ""
       DispatchCloud:
         InternalURLs: {SAMPLE: {ListenURL: ""}}
         ExternalURL: ""
@@ -340,7 +334,6 @@ Clusters:
       # AutoSetupUsernameBlacklist is a list of usernames to be blacklisted for auto setup.
       AutoSetupNewUsers: false
       AutoSetupNewUsersWithVmUUID: ""
-      AutoSetupNewUsersWithRepository: false
       AutoSetupUsernameBlacklist:
         arvados: {}
         git: {}
@@ -1050,24 +1043,6 @@ Clusters:
       # production use.
       TrustPrivateNetworks: false
 
-    Git:
-      # Path to git or gitolite-shell executable. Each authenticated
-      # request will execute this program with the single argument "http-backend"
-      GitCommand: /usr/bin/git
-
-      # Path to Gitolite's home directory. If a non-empty path is given,
-      # the CGI environment will be set up to support the use of
-      # gitolite-shell as a GitCommand: for example, if GitoliteHome is
-      # "/gh", then the CGI environment will have GITOLITE_HTTP_HOME=/gh,
-      # PATH=$PATH:/gh/bin, and GL_BYPASS_ACCESS_CHECKS=1.
-      GitoliteHome: ""
-
-      # Git repositories must be readable by api server, or you won't be
-      # able to submit crunch jobs. To pass the test suites, put a clone
-      # of the arvados tree in {git_repositories_dir}/arvados.git or
-      # {git_repositories_dir}/arvados/.git
-      Repositories: /var/lib/arvados/git/repositories
-
     TLS:
       # Use "file:///var/lib/acme/live/example.com/cert" and
       # ".../privkey" to load externally managed certificates.
@@ -1394,12 +1369,6 @@ Clusters:
         # 'false' -- disable the Jobs API despite presence of existing records.
         Enable: 'auto'
 
-        # Git repositories must be readable by api server, or you won't be
-        # able to submit crunch jobs. To pass the test suites, put a clone
-        # of the arvados tree in {git_repositories_dir}/arvados.git or
-        # {git_repositories_dir}/arvados/.git
-        GitInternalDir: /var/lib/arvados/internal.git
-
       CloudVMs:
         # Enable the cloud scheduler.
         Enable: false
diff --git a/lib/config/deprecated.go b/lib/config/deprecated.go
index d518b3414a..0db3de7fc9 100644
--- a/lib/config/deprecated.go
+++ b/lib/config/deprecated.go
@@ -510,56 +510,6 @@ func (ldr *Loader) loadOldKeepWebConfig(cfg *arvados.Config) error {
 	return nil
 }
 
-const defaultGitHttpdConfigPath = "/etc/arvados/git-httpd/git-httpd.yml"
-
-type oldGitHttpdConfig struct {
-	Client          *arvados.Client
-	Listen          *string
-	GitCommand      *string
-	GitoliteHome    *string
-	RepoRoot        *string
-	ManagementToken *string
-}
-
-func (ldr *Loader) loadOldGitHttpdConfig(cfg *arvados.Config) error {
-	if ldr.GitHttpdPath == "" {
-		return nil
-	}
-	var oc oldGitHttpdConfig
-	err := ldr.loadOldConfigHelper("arvados-git-httpd", ldr.GitHttpdPath, &oc)
-	if os.IsNotExist(err) && ldr.GitHttpdPath == defaultGitHttpdConfigPath {
-		return nil
-	} else if err != nil {
-		return err
-	}
-
-	cluster, err := cfg.GetCluster("")
-	if err != nil {
-		return err
-	}
-
-	loadOldClientConfig(cluster, oc.Client)
-
-	if oc.Listen != nil {
-		cluster.Services.GitHTTP.InternalURLs[arvados.URL{Host: *oc.Listen}] = arvados.ServiceInstance{}
-	}
-	if oc.ManagementToken != nil {
-		cluster.ManagementToken = *oc.ManagementToken
-	}
-	if oc.GitCommand != nil {
-		cluster.Git.GitCommand = *oc.GitCommand
-	}
-	if oc.GitoliteHome != nil {
-		cluster.Git.GitoliteHome = *oc.GitoliteHome
-	}
-	if oc.RepoRoot != nil {
-		cluster.Git.Repositories = *oc.RepoRoot
-	}
-
-	cfg.Clusters[cluster.ClusterID] = *cluster
-	return nil
-}
-
 const defaultKeepBalanceConfigPath = "/etc/arvados/keep-balance/keep-balance.yml"
 
 type oldKeepBalanceConfig struct {
diff --git a/lib/config/deprecated_test.go b/lib/config/deprecated_test.go
index e06a1f231d..f73a92be5c 100644
--- a/lib/config/deprecated_test.go
+++ b/lib/config/deprecated_test.go
@@ -283,52 +283,6 @@ func fmtKeepproxyConfig(param string, debugLog bool) string {
 `, debugLog, param)
 }
 
-func (s *LoadSuite) TestLegacyArvGitHttpdConfig(c *check.C) {
-	content := []byte(`
-{
-	"Client": {
-		"Scheme": "",
-		"APIHost": "example.com",
-		"AuthToken": "abcdefg",
-	},
-	"Listen": ":9000",
-	"GitCommand": "/test/git",
-	"GitoliteHome": "/test/gitolite",
-	"RepoRoot": "/test/reporoot",
-	"ManagementToken": "xyzzy"
-}
-`)
-	f := "-legacy-git-httpd-config"
-	cluster, err := testLoadLegacyConfig(content, f, c)
-
-	c.Assert(err, check.IsNil)
-	c.Assert(cluster, check.NotNil)
-	c.Check(cluster.Services.Controller.ExternalURL, check.Equals, arvados.URL{Scheme: "https", Host: "example.com", Path: "/"})
-	c.Check(cluster.SystemRootToken, check.Equals, "abcdefg")
-	c.Check(cluster.ManagementToken, check.Equals, "xyzzy")
-	c.Check(cluster.Git.GitCommand, check.Equals, "/test/git")
-	c.Check(cluster.Git.GitoliteHome, check.Equals, "/test/gitolite")
-	c.Check(cluster.Git.Repositories, check.Equals, "/test/reporoot")
-	c.Check(cluster.Services.Keepproxy.InternalURLs[arvados.URL{Host: ":9000"}], check.Equals, arvados.ServiceInstance{})
-}
-
-// Tests fix for https://dev.arvados.org/issues/15642
-func (s *LoadSuite) TestLegacyArvGitHttpdConfigDoesntDisableMissingItems(c *check.C) {
-	content := []byte(`
-{
-	"Client": {
-		"Scheme": "",
-		"APIHost": "example.com",
-		"AuthToken": "abcdefg",
-	}
-}
-`)
-	cluster, err := testLoadLegacyConfig(content, "-legacy-git-httpd-config", c)
-	c.Assert(err, check.IsNil)
-	// The resulting ManagementToken should be the one set up on the test server.
-	c.Check(cluster.ManagementToken, check.Equals, TestServerManagementToken)
-}
-
 func (s *LoadSuite) TestLegacyKeepBalanceConfig(c *check.C) {
 	f := "-legacy-keepbalance-config"
 	content := []byte(fmtKeepBalanceConfig(""))
diff --git a/lib/config/export.go b/lib/config/export.go
index 4b6c142ff2..d7939a6ef2 100644
--- a/lib/config/export.go
+++ b/lib/config/export.go
@@ -59,107 +59,105 @@ func ExportJSON(w io.Writer, cluster *arvados.Cluster) error {
 // exists.
 var whitelist = map[string]bool{
 	// | sort -t'"' -k2,2
-	"API":                                      true,
-	"API.AsyncPermissionsUpdateInterval":       false,
-	"API.DisabledAPIs":                         false,
-	"API.FreezeProjectRequiresDescription":     true,
-	"API.FreezeProjectRequiresProperties":      true,
-	"API.FreezeProjectRequiresProperties.*":    true,
-	"API.KeepServiceRequestTimeout":            false,
-	"API.LockBeforeUpdate":                     false,
-	"API.LogCreateRequestFraction":             false,
-	"API.MaxConcurrentRailsRequests":           false,
-	"API.MaxConcurrentRequests":                false,
-	"API.MaxGatewayTunnels":                    false,
-	"API.MaxIndexDatabaseRead":                 false,
-	"API.MaxItemsPerResponse":                  true,
-	"API.MaxKeepBlobBuffers":                   false,
-	"API.MaxQueuedRequests":                    false,
-	"API.MaxQueueTimeForLockRequests":          false,
-	"API.MaxRequestAmplification":              false,
-	"API.MaxRequestSize":                       true,
-	"API.MaxTokenLifetime":                     false,
-	"API.RequestTimeout":                       true,
-	"API.SendTimeout":                          true,
-	"API.UnfreezeProjectRequiresAdmin":         true,
-	"API.VocabularyPath":                       false,
-	"API.WebsocketClientEventQueue":            false,
-	"API.WebsocketServerEventQueue":            false,
-	"AuditLogs":                                false,
-	"AuditLogs.MaxAge":                         false,
-	"AuditLogs.MaxDeleteBatch":                 false,
-	"AuditLogs.UnloggedAttributes":             false,
-	"ClusterID":                                true,
-	"Collections":                              true,
-	"Collections.BalanceCollectionBatch":       false,
-	"Collections.BalanceCollectionBuffers":     false,
-	"Collections.BalancePeriod":                false,
-	"Collections.BalancePullLimit":             false,
-	"Collections.BalanceTimeout":               false,
-	"Collections.BalanceTrashLimit":            false,
-	"Collections.BalanceUpdateLimit":           false,
-	"Collections.BlobDeleteConcurrency":        false,
-	"Collections.BlobMissingReport":            false,
-	"Collections.BlobReplicateConcurrency":     false,
-	"Collections.BlobSigning":                  true,
-	"Collections.BlobSigningKey":               false,
-	"Collections.BlobSigningTTL":               true,
-	"Collections.BlobTrash":                    false,
-	"Collections.BlobTrashCheckInterval":       false,
-	"Collections.BlobTrashConcurrency":         false,
-	"Collections.BlobTrashLifetime":            false,
-	"Collections.CollectionVersioning":         true,
-	"Collections.DefaultReplication":           true,
-	"Collections.DefaultTrashLifetime":         true,
-	"Collections.ForwardSlashNameSubstitution": true,
-	"Collections.KeepproxyPermission":          false,
-	"Collections.ManagedProperties":            true,
-	"Collections.ManagedProperties.*":          true,
-	"Collections.ManagedProperties.*.*":        true,
-	"Collections.PreserveVersionIfIdle":        true,
-	"Collections.S3FolderObjects":              true,
-	"Collections.TrashSweepInterval":           false,
-	"Collections.TrustAllContent":              true,
-	"Collections.WebDAVCache":                  false,
-	"Collections.WebDAVLogEvents":              false,
-	"Collections.WebDAVPermission":             false,
-	"Containers":                               true,
-	"Containers.AlwaysUsePreemptibleInstances": true,
-	"Containers.CloudVMs":                      false,
-	"Containers.CrunchRunArgumentsList":        false,
-	"Containers.CrunchRunCommand":              false,
-	"Containers.DefaultKeepCacheRAM":           true,
-	"Containers.DispatchPrivateKey":            false,
-	"Containers.JobsAPI":                       true,
-	"Containers.JobsAPI.Enable":                true,
-	"Containers.JobsAPI.GitInternalDir":        false,
-	"Containers.LocalKeepBlobBuffersPerVCPU":   false,
-	"Containers.LocalKeepLogsToContainerLog":   false,
-	"Containers.Logging":                       false,
-	"Containers.LogReuseDecisions":             false,
-	"Containers.LSF":                           false,
-	"Containers.MaxDispatchAttempts":           false,
-	"Containers.MaximumPriceFactor":            true,
-	"Containers.MaxRetryAttempts":              true,
-	"Containers.MinRetryPeriod":                true,
-	"Containers.PreemptiblePriceFactor":        false,
-	"Containers.ReserveExtraRAM":               true,
-	"Containers.RuntimeEngine":                 true,
-	"Containers.ShellAccess":                   true,
-	"Containers.ShellAccess.Admin":             true,
-	"Containers.ShellAccess.User":              true,
-	"Containers.SLURM":                         false,
-	"Containers.StaleLockTimeout":              false,
-	"Containers.SupportedDockerImageFormats":   true,
-	"Containers.SupportedDockerImageFormats.*": true,
-	"Git":                                  false,
-	"InstanceTypes":                        true,
-	"InstanceTypes.*":                      true,
-	"InstanceTypes.*.*":                    true,
-	"InstanceTypes.*.*.*":                  true,
-	"Login":                                true,
-	"Login.Google":                         true,
-	"Login.Google.AlternateEmailAddresses": false,
+	"API":                                                 true,
+	"API.AsyncPermissionsUpdateInterval":                  false,
+	"API.DisabledAPIs":                                    false,
+	"API.FreezeProjectRequiresDescription":                true,
+	"API.FreezeProjectRequiresProperties":                 true,
+	"API.FreezeProjectRequiresProperties.*":               true,
+	"API.KeepServiceRequestTimeout":                       false,
+	"API.LockBeforeUpdate":                                false,
+	"API.LogCreateRequestFraction":                        false,
+	"API.MaxConcurrentRailsRequests":                      false,
+	"API.MaxConcurrentRequests":                           false,
+	"API.MaxGatewayTunnels":                               false,
+	"API.MaxIndexDatabaseRead":                            false,
+	"API.MaxItemsPerResponse":                             true,
+	"API.MaxKeepBlobBuffers":                              false,
+	"API.MaxQueuedRequests":                               false,
+	"API.MaxQueueTimeForLockRequests":                     false,
+	"API.MaxRequestAmplification":                         false,
+	"API.MaxRequestSize":                                  true,
+	"API.MaxTokenLifetime":                                false,
+	"API.RequestTimeout":                                  true,
+	"API.SendTimeout":                                     true,
+	"API.UnfreezeProjectRequiresAdmin":                    true,
+	"API.VocabularyPath":                                  false,
+	"API.WebsocketClientEventQueue":                       false,
+	"API.WebsocketServerEventQueue":                       false,
+	"AuditLogs":                                           false,
+	"AuditLogs.MaxAge":                                    false,
+	"AuditLogs.MaxDeleteBatch":                            false,
+	"AuditLogs.UnloggedAttributes":                        false,
+	"ClusterID":                                           true,
+	"Collections":                                         true,
+	"Collections.BalanceCollectionBatch":                  false,
+	"Collections.BalanceCollectionBuffers":                false,
+	"Collections.BalancePeriod":                           false,
+	"Collections.BalancePullLimit":                        false,
+	"Collections.BalanceTimeout":                          false,
+	"Collections.BalanceTrashLimit":                       false,
+	"Collections.BalanceUpdateLimit":                      false,
+	"Collections.BlobDeleteConcurrency":                   false,
+	"Collections.BlobMissingReport":                       false,
+	"Collections.BlobReplicateConcurrency":                false,
+	"Collections.BlobSigning":                             true,
+	"Collections.BlobSigningKey":                          false,
+	"Collections.BlobSigningTTL":                          true,
+	"Collections.BlobTrash":                               false,
+	"Collections.BlobTrashCheckInterval":                  false,
+	"Collections.BlobTrashConcurrency":                    false,
+	"Collections.BlobTrashLifetime":                       false,
+	"Collections.CollectionVersioning":                    true,
+	"Collections.DefaultReplication":                      true,
+	"Collections.DefaultTrashLifetime":                    true,
+	"Collections.ForwardSlashNameSubstitution":            true,
+	"Collections.KeepproxyPermission":                     false,
+	"Collections.ManagedProperties":                       true,
+	"Collections.ManagedProperties.*":                     true,
+	"Collections.ManagedProperties.*.*":                   true,
+	"Collections.PreserveVersionIfIdle":                   true,
+	"Collections.S3FolderObjects":                         true,
+	"Collections.TrashSweepInterval":                      false,
+	"Collections.TrustAllContent":                         true,
+	"Collections.WebDAVCache":                             false,
+	"Collections.WebDAVLogEvents":                         false,
+	"Collections.WebDAVPermission":                        false,
+	"Containers":                                          true,
+	"Containers.AlwaysUsePreemptibleInstances":            true,
+	"Containers.CloudVMs":                                 false,
+	"Containers.CrunchRunArgumentsList":                   false,
+	"Containers.CrunchRunCommand":                         false,
+	"Containers.DefaultKeepCacheRAM":                      true,
+	"Containers.DispatchPrivateKey":                       false,
+	"Containers.JobsAPI":                                  true,
+	"Containers.JobsAPI.Enable":                           true,
+	"Containers.LocalKeepBlobBuffersPerVCPU":              false,
+	"Containers.LocalKeepLogsToContainerLog":              false,
+	"Containers.Logging":                                  false,
+	"Containers.LogReuseDecisions":                        false,
+	"Containers.LSF":                                      false,
+	"Containers.MaxDispatchAttempts":                      false,
+	"Containers.MaximumPriceFactor":                       true,
+	"Containers.MaxRetryAttempts":                         true,
+	"Containers.MinRetryPeriod":                           true,
+	"Containers.PreemptiblePriceFactor":                   false,
+	"Containers.ReserveExtraRAM":                          true,
+	"Containers.RuntimeEngine":                            true,
+	"Containers.ShellAccess":                              true,
+	"Containers.ShellAccess.Admin":                        true,
+	"Containers.ShellAccess.User":                         true,
+	"Containers.SLURM":                                    false,
+	"Containers.StaleLockTimeout":                         false,
+	"Containers.SupportedDockerImageFormats":              true,
+	"Containers.SupportedDockerImageFormats.*":            true,
+	"InstanceTypes":                                       true,
+	"InstanceTypes.*":                                     true,
+	"InstanceTypes.*.*":                                   true,
+	"InstanceTypes.*.*.*":                                 true,
+	"Login":                                               true,
+	"Login.Google":                                        true,
+	"Login.Google.AlternateEmailAddresses":                false,
 	"Login.Google.AuthenticationRequestParameters":        false,
 	"Login.Google.ClientID":                               false,
 	"Login.Google.ClientSecret":                           false,
@@ -242,7 +240,6 @@ var whitelist = map[string]bool{
 	"Users.AutoAdminFirstUser":                            false,
 	"Users.AutoAdminUserWithEmail":                        false,
 	"Users.AutoSetupNewUsers":                             false,
-	"Users.AutoSetupNewUsersWithRepository":               false,
 	"Users.AutoSetupNewUsersWithVmUUID":                   false,
 	"Users.AutoSetupUsernameBlacklist":                    false,
 	"Users.CanCreateRoleGroups":                           true,
diff --git a/lib/config/load.go b/lib/config/load.go
index d504f7796c..00c8e28683 100644
--- a/lib/config/load.go
+++ b/lib/config/load.go
@@ -48,7 +48,6 @@ type Loader struct {
 	CrunchDispatchSlurmPath string
 	WebsocketPath           string
 	KeepproxyPath           string
-	GitHttpdPath            string
 	KeepBalancePath         string
 
 	configdata []byte
@@ -88,7 +87,6 @@ func (ldr *Loader) SetupFlags(flagset *flag.FlagSet) {
 		flagset.StringVar(&ldr.CrunchDispatchSlurmPath, "legacy-crunch-dispatch-slurm-config", defaultCrunchDispatchSlurmConfigPath, "Legacy crunch-dispatch-slurm configuration `file`")
 		flagset.StringVar(&ldr.WebsocketPath, "legacy-ws-config", defaultWebsocketConfigPath, "Legacy arvados-ws configuration `file`")
 		flagset.StringVar(&ldr.KeepproxyPath, "legacy-keepproxy-config", defaultKeepproxyConfigPath, "Legacy keepproxy configuration `file`")
-		flagset.StringVar(&ldr.GitHttpdPath, "legacy-git-httpd-config", defaultGitHttpdConfigPath, "Legacy arvados-git-httpd configuration `file`")
 		flagset.StringVar(&ldr.KeepBalancePath, "legacy-keepbalance-config", defaultKeepBalanceConfigPath, "Legacy keep-balance configuration `file`")
 		flagset.BoolVar(&ldr.SkipLegacy, "skip-legacy", false, "Don't load legacy config files")
 	}
@@ -168,9 +166,6 @@ func (ldr *Loader) MungeLegacyConfigArgs(lgr logrus.FieldLogger, args []string,
 	if legacyConfigArg != "-legacy-keepproxy-config" {
 		ldr.KeepproxyPath = ""
 	}
-	if legacyConfigArg != "-legacy-git-httpd-config" {
-		ldr.GitHttpdPath = ""
-	}
 	if legacyConfigArg != "-legacy-keepbalance-config" {
 		ldr.KeepBalancePath = ""
 	}
@@ -296,7 +291,6 @@ func (ldr *Loader) Load() (*arvados.Config, error) {
 			ldr.loadOldCrunchDispatchSlurmConfig,
 			ldr.loadOldWebsocketConfig,
 			ldr.loadOldKeepproxyConfig,
-			ldr.loadOldGitHttpdConfig,
 			ldr.loadOldKeepBalanceConfig,
 		)
 	}
diff --git a/lib/controller/integration_test.go b/lib/controller/integration_test.go
index 70106ed53e..6b603f178e 100644
--- a/lib/controller/integration_test.go
+++ b/lib/controller/integration_test.go
@@ -826,7 +826,6 @@ func (s *IntegrationSuite) TestFederatedApiClientAuthHandling(c *check.C) {
 // Test for bug #18076
 func (s *IntegrationSuite) TestStaleCachedUserRecord(c *check.C) {
 	rootctx1, _, _ := s.super.RootClients("z1111")
-	_, rootclnt3, _ := s.super.RootClients("z3333")
 	conn1 := s.super.Conn("z1111")
 	conn3 := s.super.Conn("z3333")
 
@@ -838,92 +837,69 @@ func (s *IntegrationSuite) TestStaleCachedUserRecord(c *check.C) {
 			check.Commentf("incorrect LoginCluster config on cluster %q", cls))
 	}
 
-	for testCaseNr, testCase := range []struct {
-		name           string
-		withRepository bool
-	}{
-		{"User without local repository", false},
-		{"User with local repository", true},
-	} {
-		c.Log(c.TestName() + " " + testCase.name)
-		// Create some users, request them on the federated cluster so they're cached.
-		var users []arvados.User
-		for userNr := 0; userNr < 2; userNr++ {
-			_, _, _, user := s.super.UserClients("z1111",
-				rootctx1,
-				c,
-				conn1,
-				fmt.Sprintf("user%d%d at example.com", testCaseNr, userNr),
-				true)
-			c.Assert(user.Username, check.Not(check.Equals), "")
-			users = append(users, user)
-
-			lst, err := conn3.UserList(rootctx1, arvados.ListOptions{Limit: -1})
-			c.Assert(err, check.Equals, nil)
-			userFound := false
-			for _, fedUser := range lst.Items {
-				if fedUser.UUID == user.UUID {
-					c.Assert(fedUser.Username, check.Equals, user.Username)
-					userFound = true
-					break
-				}
-			}
-			c.Assert(userFound, check.Equals, true)
-
-			if testCase.withRepository {
-				var repo interface{}
-				err = rootclnt3.RequestAndDecode(
-					&repo, "POST", "arvados/v1/repositories", nil,
-					map[string]interface{}{
-						"repository": map[string]string{
-							"name":       fmt.Sprintf("%s/test", user.Username),
-							"owner_uuid": user.UUID,
-						},
-					},
-				)
-				c.Assert(err, check.IsNil)
-			}
-		}
+	// Create some users, request them on the federated cluster so they're cached.
+	var users []arvados.User
+	for userNr := 0; userNr < 2; userNr++ {
+		_, _, _, user := s.super.UserClients("z1111",
+			rootctx1,
+			c,
+			conn1,
+			fmt.Sprintf("user0%d at example.com", userNr),
+			true)
+		c.Assert(user.Username, check.Not(check.Equals), "")
+		users = append(users, user)
 
-		// Swap the usernames
-		_, err := conn1.UserUpdate(rootctx1, arvados.UpdateOptions{
-			UUID: users[0].UUID,
-			Attrs: map[string]interface{}{
-				"username": "",
-			},
-		})
-		c.Assert(err, check.Equals, nil)
-		_, err = conn1.UserUpdate(rootctx1, arvados.UpdateOptions{
-			UUID: users[1].UUID,
-			Attrs: map[string]interface{}{
-				"username": users[0].Username,
-			},
-		})
-		c.Assert(err, check.Equals, nil)
-		_, err = conn1.UserUpdate(rootctx1, arvados.UpdateOptions{
-			UUID: users[0].UUID,
-			Attrs: map[string]interface{}{
-				"username": users[1].Username,
-			},
-		})
-		c.Assert(err, check.Equals, nil)
-
-		// Re-request the list on the federated cluster & check for updates
 		lst, err := conn3.UserList(rootctx1, arvados.ListOptions{Limit: -1})
 		c.Assert(err, check.Equals, nil)
-		var user0Found, user1Found bool
-		for _, user := range lst.Items {
-			if user.UUID == users[0].UUID {
-				user0Found = true
-				c.Assert(user.Username, check.Equals, users[1].Username)
-			} else if user.UUID == users[1].UUID {
-				user1Found = true
-				c.Assert(user.Username, check.Equals, users[0].Username)
+		userFound := false
+		for _, fedUser := range lst.Items {
+			if fedUser.UUID == user.UUID {
+				c.Assert(fedUser.Username, check.Equals, user.Username)
+				userFound = true
+				break
 			}
 		}
-		c.Assert(user0Found, check.Equals, true)
-		c.Assert(user1Found, check.Equals, true)
+		c.Assert(userFound, check.Equals, true)
+	}
+
+	// Swap the usernames
+	_, err := conn1.UserUpdate(rootctx1, arvados.UpdateOptions{
+		UUID: users[0].UUID,
+		Attrs: map[string]interface{}{
+			"username": "",
+		},
+	})
+	c.Assert(err, check.Equals, nil)
+	_, err = conn1.UserUpdate(rootctx1, arvados.UpdateOptions{
+		UUID: users[1].UUID,
+		Attrs: map[string]interface{}{
+			"username": users[0].Username,
+		},
+	})
+	c.Assert(err, check.Equals, nil)
+	_, err = conn1.UserUpdate(rootctx1, arvados.UpdateOptions{
+		UUID: users[0].UUID,
+		Attrs: map[string]interface{}{
+			"username": users[1].Username,
+		},
+	})
+	c.Assert(err, check.Equals, nil)
+
+	// Re-request the list on the federated cluster & check for updates
+	lst, err := conn3.UserList(rootctx1, arvados.ListOptions{Limit: -1})
+	c.Assert(err, check.Equals, nil)
+	var user0Found, user1Found bool
+	for _, user := range lst.Items {
+		if user.UUID == users[0].UUID {
+			user0Found = true
+			c.Assert(user.Username, check.Equals, users[1].Username)
+		} else if user.UUID == users[1].UUID {
+			user1Found = true
+			c.Assert(user.Username, check.Equals, users[0].Username)
+		}
 	}
+	c.Assert(user0Found, check.Equals, true)
+	c.Assert(user1Found, check.Equals, true)
 }
 
 // Test for bug #16263
diff --git a/lib/crunchrun/crunchrun.go b/lib/crunchrun/crunchrun.go
index 3233ee7e57..607d569e23 100644
--- a/lib/crunchrun/crunchrun.go
+++ b/lib/crunchrun/crunchrun.go
@@ -626,17 +626,6 @@ func (runner *ContainerRunner) SetupMounts() (map[string]bindmount, error) {
 				// OutputPath is a staging directory.
 				bindmounts[bind] = bindmount{HostPath: tmpfn, ReadOnly: true}
 			}
-
-		case mnt.Kind == "git_tree":
-			tmpdir, err := runner.MkTempDir(runner.parentTemp, "git_tree")
-			if err != nil {
-				return nil, fmt.Errorf("creating temp dir: %v", err)
-			}
-			err = gitMount(mnt).extractTree(runner.containerClient, tmpdir, token)
-			if err != nil {
-				return nil, err
-			}
-			bindmounts[bind] = bindmount{HostPath: tmpdir, ReadOnly: true}
 		}
 	}
 
diff --git a/lib/crunchrun/crunchrun_test.go b/lib/crunchrun/crunchrun_test.go
index 1188fe296a..5cb982e1bb 100644
--- a/lib/crunchrun/crunchrun_test.go
+++ b/lib/crunchrun/crunchrun_test.go
@@ -40,8 +40,6 @@ import (
 	"git.arvados.org/arvados.git/sdk/go/manifest"
 
 	. "gopkg.in/check.v1"
-	git_client "gopkg.in/src-d/go-git.v4/plumbing/transport/client"
-	git_http "gopkg.in/src-d/go-git.v4/plumbing/transport/http"
 )
 
 // Gocheck boilerplate
@@ -1752,54 +1750,6 @@ func (s *TestSuite) TestSetupMounts(c *C) {
 		cr.CleanupDirs()
 		checkEmpty()
 	}
-
-	// git_tree mounts
-	{
-		i = 0
-		cr.ArvMountPoint = ""
-		git_client.InstallProtocol("https", git_http.NewClient(arvados.InsecureHTTPClient))
-		cr.token = arvadostest.ActiveToken
-		cr.Container.Mounts = make(map[string]arvados.Mount)
-		cr.Container.Mounts = map[string]arvados.Mount{
-			"/tip": {
-				Kind:   "git_tree",
-				UUID:   arvadostest.Repository2UUID,
-				Commit: "fd3531f42995344f36c30b79f55f27b502f3d344",
-				Path:   "/",
-			},
-			"/non-tip": {
-				Kind:   "git_tree",
-				UUID:   arvadostest.Repository2UUID,
-				Commit: "5ebfab0522851df01fec11ec55a6d0f4877b542e",
-				Path:   "/",
-			},
-		}
-		cr.Container.OutputPath = "/tmp"
-
-		bindmounts, err := cr.SetupMounts()
-		c.Check(err, IsNil)
-
-		for path, mount := range bindmounts {
-			c.Check(mount.ReadOnly, Equals, !cr.Container.Mounts[path].Writable, Commentf("%s %#v", path, mount))
-		}
-
-		data, err := ioutil.ReadFile(bindmounts["/tip"].HostPath + "/dir1/dir2/file with mode 0644")
-		c.Check(err, IsNil)
-		c.Check(string(data), Equals, "\000\001\002\003")
-		_, err = ioutil.ReadFile(bindmounts["/tip"].HostPath + "/file only on testbranch")
-		c.Check(err, FitsTypeOf, &os.PathError{})
-		c.Check(os.IsNotExist(err), Equals, true)
-
-		data, err = ioutil.ReadFile(bindmounts["/non-tip"].HostPath + "/dir1/dir2/file with mode 0644")
-		c.Check(err, IsNil)
-		c.Check(string(data), Equals, "\000\001\002\003")
-		data, err = ioutil.ReadFile(bindmounts["/non-tip"].HostPath + "/file only on testbranch")
-		c.Check(err, IsNil)
-		c.Check(string(data), Equals, "testfile\n")
-
-		cr.CleanupDirs()
-		checkEmpty()
-	}
 }
 
 func (s *TestSuite) TestStdout(c *C) {
diff --git a/lib/crunchrun/git_mount.go b/lib/crunchrun/git_mount.go
deleted file mode 100644
index 561ea18de4..0000000000
--- a/lib/crunchrun/git_mount.go
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-package crunchrun
-
-import (
-	"fmt"
-	"net/url"
-	"os"
-	"path/filepath"
-	"regexp"
-
-	"git.arvados.org/arvados.git/sdk/go/arvados"
-	"gopkg.in/src-d/go-billy.v4/osfs"
-	git "gopkg.in/src-d/go-git.v4"
-	git_config "gopkg.in/src-d/go-git.v4/config"
-	git_plumbing "gopkg.in/src-d/go-git.v4/plumbing"
-	git_http "gopkg.in/src-d/go-git.v4/plumbing/transport/http"
-	"gopkg.in/src-d/go-git.v4/storage/memory"
-)
-
-type gitMount arvados.Mount
-
-var (
-	sha1re     = regexp.MustCompile(`^[0-9a-f]{40}$`)
-	repoUUIDre = regexp.MustCompile(`^[0-9a-z]{5}-s0uqq-[0-9a-z]{15}$`)
-)
-
-func (gm gitMount) validate() error {
-	if gm.Path != "" && gm.Path != "/" {
-		return fmt.Errorf("cannot mount git_tree with path %q -- only \"/\" is supported", gm.Path)
-	}
-	if !sha1re.MatchString(gm.Commit) {
-		return fmt.Errorf("cannot mount git_tree with commit %q -- must be a 40-char SHA1", gm.Commit)
-	}
-	if gm.RepositoryName != "" || gm.GitURL != "" {
-		return fmt.Errorf("cannot mount git_tree -- repository_name and git_url must be empty")
-	}
-	if !repoUUIDre.MatchString(gm.UUID) {
-		return fmt.Errorf("cannot mount git_tree with uuid %q -- must be a repository UUID", gm.UUID)
-	}
-	if gm.Writable {
-		return fmt.Errorf("writable git_tree mount is not supported")
-	}
-	return nil
-}
-
-// ExtractTree extracts the specified tree into dir, which is an
-// existing empty local directory.
-func (gm gitMount) extractTree(ac *arvados.Client, dir string, token string) error {
-	err := gm.validate()
-	if err != nil {
-		return err
-	}
-	dd, err := ac.DiscoveryDocument()
-	if err != nil {
-		return fmt.Errorf("error getting discovery document: %w", err)
-	}
-	u, err := url.Parse(dd.GitURL)
-	if err != nil {
-		return fmt.Errorf("parse gitUrl %q: %s", dd.GitURL, err)
-	}
-	u, err = u.Parse("/" + gm.UUID + ".git")
-	if err != nil {
-		return fmt.Errorf("build git url from %q, %q: %s", dd.GitURL, gm.UUID, err)
-	}
-	store := memory.NewStorage()
-	repo, err := git.Init(store, osfs.New(dir))
-	if err != nil {
-		return fmt.Errorf("init repo: %s", err)
-	}
-	_, err = repo.CreateRemote(&git_config.RemoteConfig{
-		Name: "origin",
-		URLs: []string{u.String()},
-	})
-	if err != nil {
-		return fmt.Errorf("create remote %q: %s", u.String(), err)
-	}
-	err = repo.Fetch(&git.FetchOptions{
-		RemoteName: "origin",
-		Auth: &git_http.BasicAuth{
-			Username: "none",
-			Password: token,
-		},
-	})
-	if err != nil {
-		return fmt.Errorf("git fetch %q: %s", u.String(), err)
-	}
-	wt, err := repo.Worktree()
-	if err != nil {
-		return fmt.Errorf("worktree failed: %s", err)
-	}
-	err = wt.Checkout(&git.CheckoutOptions{
-		Hash: git_plumbing.NewHash(gm.Commit),
-	})
-	if err != nil {
-		return fmt.Errorf("checkout failed: %s", err)
-	}
-	err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
-		if err != nil {
-			return err
-		}
-		// copy user rx bits to group and other, in case
-		// prevailing umask is more restrictive than 022
-		mode := info.Mode()
-		mode = mode | ((mode >> 3) & 050) | ((mode >> 6) & 5)
-		return os.Chmod(path, mode)
-	})
-	if err != nil {
-		return fmt.Errorf("chmod -R %q: %s", dir, err)
-	}
-	return nil
-}
diff --git a/lib/crunchrun/git_mount_test.go b/lib/crunchrun/git_mount_test.go
deleted file mode 100644
index ac98dcc480..0000000000
--- a/lib/crunchrun/git_mount_test.go
+++ /dev/null
@@ -1,201 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-package crunchrun
-
-import (
-	"io/ioutil"
-	"os"
-	"path/filepath"
-
-	"git.arvados.org/arvados.git/sdk/go/arvados"
-	"git.arvados.org/arvados.git/sdk/go/arvadostest"
-	check "gopkg.in/check.v1"
-	git_client "gopkg.in/src-d/go-git.v4/plumbing/transport/client"
-	git_http "gopkg.in/src-d/go-git.v4/plumbing/transport/http"
-)
-
-type GitMountSuite struct {
-	tmpdir string
-}
-
-var _ = check.Suite(&GitMountSuite{})
-
-func (s *GitMountSuite) SetUpTest(c *check.C) {
-	var err error
-	s.tmpdir, err = ioutil.TempDir("", "")
-	c.Assert(err, check.IsNil)
-	git_client.InstallProtocol("https", git_http.NewClient(arvados.InsecureHTTPClient))
-}
-
-func (s *GitMountSuite) TearDownTest(c *check.C) {
-	err := os.RemoveAll(s.tmpdir)
-	c.Check(err, check.IsNil)
-}
-
-// Commit fd3531f is crunch-run-tree-test
-func (s *GitMountSuite) TestExtractTree(c *check.C) {
-	gm := gitMount{
-		Path:   "/",
-		UUID:   arvadostest.Repository2UUID,
-		Commit: "fd3531f42995344f36c30b79f55f27b502f3d344",
-	}
-	ac := arvados.NewClientFromEnv()
-	err := gm.extractTree(ac, s.tmpdir, arvadostest.ActiveToken)
-	c.Check(err, check.IsNil)
-
-	fnm := filepath.Join(s.tmpdir, "dir1/dir2/file with mode 0644")
-	data, err := ioutil.ReadFile(fnm)
-	c.Check(err, check.IsNil)
-	c.Check(data, check.DeepEquals, []byte{0, 1, 2, 3})
-	fi, err := os.Stat(fnm)
-	c.Check(err, check.IsNil)
-	if err == nil {
-		c.Check(fi.Mode(), check.Equals, os.FileMode(0644))
-	}
-
-	fnm = filepath.Join(s.tmpdir, "dir1/dir2/file with mode 0755")
-	data, err = ioutil.ReadFile(fnm)
-	c.Check(err, check.IsNil)
-	c.Check(string(data), check.DeepEquals, "#!/bin/sh\nexec echo OK\n")
-	fi, err = os.Stat(fnm)
-	c.Check(err, check.IsNil)
-	if err == nil {
-		c.Check(fi.Mode(), check.Equals, os.FileMode(0755))
-	}
-
-	// Ensure there's no extra stuff like a ".git" dir
-	s.checkTmpdirContents(c, []string{"dir1"})
-
-	// Ensure tmpdir is world-readable and world-executable so the
-	// UID inside the container can use it.
-	fi, err = os.Stat(s.tmpdir)
-	c.Check(err, check.IsNil)
-	c.Check(fi.Mode()&os.ModePerm, check.Equals, os.FileMode(0755))
-}
-
-// Commit 5ebfab0 is not the tip of any branch or tag, but is
-// reachable in branch "crunch-run-non-tip-test".
-func (s *GitMountSuite) TestExtractNonTipCommit(c *check.C) {
-	gm := gitMount{
-		UUID:   arvadostest.Repository2UUID,
-		Commit: "5ebfab0522851df01fec11ec55a6d0f4877b542e",
-	}
-	err := gm.extractTree(arvados.NewClientFromEnv(), s.tmpdir, arvadostest.ActiveToken)
-	c.Check(err, check.IsNil)
-
-	fnm := filepath.Join(s.tmpdir, "file only on testbranch")
-	data, err := ioutil.ReadFile(fnm)
-	c.Check(err, check.IsNil)
-	c.Check(string(data), check.DeepEquals, "testfile\n")
-}
-
-func (s *GitMountSuite) TestNonexistentRepository(c *check.C) {
-	gm := gitMount{
-		Path:   "/",
-		UUID:   "zzzzz-s0uqq-nonexistentrepo",
-		Commit: "5ebfab0522851df01fec11ec55a6d0f4877b542e",
-	}
-	err := gm.extractTree(arvados.NewClientFromEnv(), s.tmpdir, arvadostest.ActiveToken)
-	c.Check(err, check.NotNil)
-	c.Check(err, check.ErrorMatches, ".*repository not found.*")
-
-	s.checkTmpdirContents(c, []string{})
-}
-
-func (s *GitMountSuite) TestNonexistentCommit(c *check.C) {
-	gm := gitMount{
-		Path:   "/",
-		UUID:   arvadostest.Repository2UUID,
-		Commit: "bb66b6bb6b6bbb6b6b6b66b6b6b6b6b6b6b6b66b",
-	}
-	err := gm.extractTree(arvados.NewClientFromEnv(), s.tmpdir, arvadostest.ActiveToken)
-	c.Check(err, check.NotNil)
-	c.Check(err, check.ErrorMatches, ".*object not found.*")
-
-	s.checkTmpdirContents(c, []string{})
-}
-
-func (s *GitMountSuite) TestGitUrlDiscoveryFails(c *check.C) {
-	delete(discoveryMap, "gitUrl")
-	gm := gitMount{
-		Path:   "/",
-		UUID:   arvadostest.Repository2UUID,
-		Commit: "5ebfab0522851df01fec11ec55a6d0f4877b542e",
-	}
-	err := gm.extractTree(&arvados.Client{}, s.tmpdir, arvadostest.ActiveToken)
-	c.Check(err, check.ErrorMatches, ".*error getting discovery doc.*")
-}
-
-func (s *GitMountSuite) TestInvalid(c *check.C) {
-	for _, trial := range []struct {
-		gm      gitMount
-		matcher string
-	}{
-		{
-			gm: gitMount{
-				Path:   "/",
-				UUID:   arvadostest.Repository2UUID,
-				Commit: "abc123",
-			},
-			matcher: ".*SHA1.*",
-		},
-		{
-			gm: gitMount{
-				Path:           "/",
-				UUID:           arvadostest.Repository2UUID,
-				RepositoryName: arvadostest.Repository2Name,
-				Commit:         "5ebfab0522851df01fec11ec55a6d0f4877b542e",
-			},
-			matcher: ".*repository_name.*",
-		},
-		{
-			gm: gitMount{
-				Path:   "/",
-				GitURL: "https://localhost:0/" + arvadostest.Repository2Name + ".git",
-				Commit: "5ebfab0522851df01fec11ec55a6d0f4877b542e",
-			},
-			matcher: ".*git_url.*",
-		},
-		{
-			gm: gitMount{
-				Path:   "/dir1/",
-				UUID:   arvadostest.Repository2UUID,
-				Commit: "5ebfab0522851df01fec11ec55a6d0f4877b542e",
-			},
-			matcher: ".*path.*",
-		},
-		{
-			gm: gitMount{
-				Path:   "/",
-				Commit: "5ebfab0522851df01fec11ec55a6d0f4877b542e",
-			},
-			matcher: ".*UUID.*",
-		},
-		{
-			gm: gitMount{
-				Path:     "/",
-				UUID:     arvadostest.Repository2UUID,
-				Commit:   "5ebfab0522851df01fec11ec55a6d0f4877b542e",
-				Writable: true,
-			},
-			matcher: ".*writable.*",
-		},
-	} {
-		err := trial.gm.extractTree(arvados.NewClientFromEnv(), s.tmpdir, arvadostest.ActiveToken)
-		c.Check(err, check.NotNil)
-		s.checkTmpdirContents(c, []string{})
-
-		err = trial.gm.validate()
-		c.Check(err, check.ErrorMatches, trial.matcher)
-	}
-}
-
-func (s *GitMountSuite) checkTmpdirContents(c *check.C, expect []string) {
-	f, err := os.Open(s.tmpdir)
-	c.Check(err, check.IsNil)
-	names, err := f.Readdirnames(-1)
-	c.Check(err, check.IsNil)
-	c.Check(names, check.DeepEquals, expect)
-}
diff --git a/lib/install/deps.go b/lib/install/deps.go
index 9720a30d26..caf6314351 100644
--- a/lib/install/deps.go
+++ b/lib/install/deps.go
@@ -901,7 +901,6 @@ func prodpkgs(osv osversion) []string {
 		"curl",
 		"fuse",
 		"git",
-		"gitolite3",
 		"graphviz",
 		"haveged",
 		"libcurl3-gnutls",
diff --git a/lib/install/init.go b/lib/install/init.go
index d9b74f6a06..12ffdd7af3 100644
--- a/lib/install/init.go
+++ b/lib/install/init.go
@@ -230,10 +230,6 @@ func (initcmd *initCommand) RunCommand(prog string, args []string, stdin io.Read
       Keepbalance:
         InternalURLs:
           "http://0.0.0.0:9019/": {}
-      GitHTTP:
-        InternalURLs:
-          "http://0.0.0.0:9005/": {}
-        ExternalURL: {{printf "%q" ( print "https://" .Domain ":4445/" ) }}
       DispatchCloud:
         InternalURLs:
           "http://0.0.0.0:9006/": {}
diff --git a/lib/service/cmd.go b/lib/service/cmd.go
index 82e95fe0b4..9ed0acfb8f 100644
--- a/lib/service/cmd.go
+++ b/lib/service/cmd.go
@@ -80,9 +80,9 @@ func (c *command) RunCommand(prog string, args []string, stdin io.Reader, stdout
 	loader := config.NewLoader(stdin, log)
 	loader.SetupFlags(flags)
 
-	// prog is [keepstore, keep-web, git-httpd, ...]  but the
+	// prog is [keepstore, keep-web, ...]  but the
 	// legacy config flags are [-legacy-keepstore-config,
-	// -legacy-keepweb-config, -legacy-git-httpd-config, ...]
+	// -legacy-keepweb-config, ...]
 	legacyFlag := "-legacy-" + strings.Replace(prog, "keep-", "keep", 1) + "-config"
 	args = loader.MungeLegacyConfigArgs(log, args, legacyFlag)
 
diff --git a/sdk/go/arvados/config.go b/sdk/go/arvados/config.go
index 483301507b..18f2f0fdb1 100644
--- a/sdk/go/arvados/config.go
+++ b/sdk/go/arvados/config.go
@@ -160,11 +160,6 @@ type Cluster struct {
 		WebDAVPermission    UploadDownloadRolePermissions
 		WebDAVLogEvents     bool
 	}
-	Git struct {
-		GitCommand   string
-		GitoliteHome string
-		Repositories string
-	}
 	Login struct {
 		LDAP struct {
 			Enable             bool
@@ -247,7 +242,6 @@ type Cluster struct {
 		AutoAdminFirstUser                    bool
 		AutoAdminUserWithEmail                string
 		AutoSetupNewUsers                     bool
-		AutoSetupNewUsersWithRepository       bool
 		AutoSetupNewUsersWithVmUUID           string
 		AutoSetupUsernameBlacklist            StringSet
 		EmailSubjectPrefix                    string
@@ -355,8 +349,6 @@ type Services struct {
 	DispatchCloud  Service
 	DispatchLSF    Service
 	DispatchSLURM  Service
-	GitHTTP        Service
-	GitSSH         Service
 	Health         Service
 	Keepbalance    Service
 	Keepproxy      Service
@@ -510,8 +502,7 @@ type ContainersConfig struct {
 	LocalKeepLogsToContainerLog   string
 
 	JobsAPI struct {
-		Enable         string
-		GitInternalDir string
+		Enable string
 	}
 	Logging struct {
 		MaxAge                       Duration
@@ -660,7 +651,6 @@ const (
 	ServiceNameDispatchCloud ServiceName = "arvados-dispatch-cloud"
 	ServiceNameDispatchLSF   ServiceName = "arvados-dispatch-lsf"
 	ServiceNameDispatchSLURM ServiceName = "crunch-dispatch-slurm"
-	ServiceNameGitHTTP       ServiceName = "arvados-git-httpd"
 	ServiceNameHealth        ServiceName = "arvados-health"
 	ServiceNameKeepbalance   ServiceName = "keep-balance"
 	ServiceNameKeepproxy     ServiceName = "keepproxy"
@@ -680,7 +670,6 @@ func (svcs Services) Map() map[ServiceName]Service {
 		ServiceNameDispatchCloud: svcs.DispatchCloud,
 		ServiceNameDispatchLSF:   svcs.DispatchLSF,
 		ServiceNameDispatchSLURM: svcs.DispatchSLURM,
-		ServiceNameGitHTTP:       svcs.GitHTTP,
 		ServiceNameHealth:        svcs.Health,
 		ServiceNameKeepbalance:   svcs.Keepbalance,
 		ServiceNameKeepproxy:     svcs.Keepproxy,
diff --git a/sdk/go/arvados/container.go b/sdk/go/arvados/container.go
index 91c8fbfe29..eb5c15af46 100644
--- a/sdk/go/arvados/container.go
+++ b/sdk/go/arvados/container.go
@@ -94,9 +94,6 @@ type Mount struct {
 	Content           interface{} `json:"content"`
 	ExcludeFromOutput bool        `json:"exclude_from_output"`
 	Capacity          int64       `json:"capacity"`
-	Commit            string      `json:"commit"`          // only if kind=="git_tree"
-	RepositoryName    string      `json:"repository_name"` // only if kind=="git_tree"
-	GitURL            string      `json:"git_url"`         // only if kind=="git_tree"
 }
 
 type CUDARuntimeConstraints struct {
diff --git a/sdk/go/arvadosclient/pool.go b/sdk/go/arvadosclient/pool.go
index bb7867aef7..4272f0f759 100644
--- a/sdk/go/arvadosclient/pool.go
+++ b/sdk/go/arvadosclient/pool.go
@@ -13,8 +13,8 @@ import (
 // A ClientPool is a pool of ArvadosClients. This is useful for
 // applications that make API calls using a dynamic set of tokens,
 // like web services that pass through their own clients'
-// credentials. See arvados-git-httpd for an example, and sync.Pool
-// for more information about garbage collection.
+// credentials. See sync.Pool for more information about garbage
+// collection.
 type ClientPool struct {
 	// Initialize new clients by copying this one.
 	Prototype *ArvadosClient
diff --git a/sdk/go/health/aggregator_test.go b/sdk/go/health/aggregator_test.go
index f76f7b8ea8..d9f3faf034 100644
--- a/sdk/go/health/aggregator_test.go
+++ b/sdk/go/health/aggregator_test.go
@@ -372,7 +372,6 @@ func (s *AggregatorSuite) setAllServiceURLs(listen string) {
 		&svcs.DispatchCloud,
 		&svcs.DispatchLSF,
 		&svcs.DispatchSLURM,
-		&svcs.GitHTTP,
 		&svcs.Keepbalance,
 		&svcs.Keepproxy,
 		&svcs.Keepstore,
diff --git a/sdk/python/arvados/commands/arv_copy.py b/sdk/python/arvados/commands/arv_copy.py
index 7f5245db86..50688ee583 100755
--- a/sdk/python/arvados/commands/arv_copy.py
+++ b/sdk/python/arvados/commands/arv_copy.py
@@ -736,58 +736,6 @@ def copy_collection(obj_uuid, src, dst, args):
     c['manifest_text'] = dst_manifest.getvalue()
     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
-
-    for url in priority:
-        if url.startswith("http"):
-            u = urllib.parse.urlsplit(url)
-            baseurl = urllib.parse.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)
-            subprocess.run(
-                ['git', *git_config, 'ls-remote', url],
-                check=True,
-                env={
-                    'ARVADOS_API_TOKEN': api.api_token,
-                    'GIT_ASKPASS': '/bin/false',
-                    'HOME': os.environ['HOME'],
-                },
-                stdout=subprocess.DEVNULL,
-            )
-        except subprocess.CalledProcessError:
-            pass
-        else:
-            git_url = url
-            break
-    else:
-        raise Exception('Cannot access git repository, tried {}'
-                        .format(priority))
-
-    if git_url.startswith("http:"):
-        if allow_insecure_http:
-            logger.warning("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)
-
-
 def copy_docker_image(docker_image, docker_image_tag, src, dst, args):
     """Copy the docker image identified by docker_image and
     docker_image_tag from src to dst. Create appropriate
diff --git a/sdk/python/tests/nginx.conf b/sdk/python/tests/nginx.conf
index 446b95ca42..a382d643ef 100644
--- a/sdk/python/tests/nginx.conf
+++ b/sdk/python/tests/nginx.conf
@@ -46,22 +46,6 @@ http {
       proxy_http_version 1.1;
     }
   }
-  upstream arv-git-http {
-    server {{UPSTREAMHOST}}:{{GITPORT}};
-  }
-  server {
-    listen {{LISTENHOST}}:{{GITSSLPORT}} ssl;
-    server_name arv-git-http git.*;
-    ssl_certificate "{{SSLCERT}}";
-    ssl_certificate_key "{{SSLKEY}}";
-    location  / {
-      proxy_pass http://arv-git-http;
-      proxy_set_header Host $http_host;
-      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
-      proxy_set_header X-Forwarded-Proto https;
-      proxy_redirect off;
-    }
-  }
   upstream keepproxy {
     server {{UPSTREAMHOST}}:{{KEEPPROXYPORT}};
   }
diff --git a/sdk/python/tests/run_test_server.py b/sdk/python/tests/run_test_server.py
index 3acc234fc5..b17f569b4d 100644
--- a/sdk/python/tests/run_test_server.py
+++ b/sdk/python/tests/run_test_server.py
@@ -587,27 +587,6 @@ def stop_keep_proxy():
         return
     kill_server_pid(_pidfile('keepproxy'))
 
-def run_arv_git_httpd():
-    if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
-        return
-    stop_arv_git_httpd()
-
-    gitport = internal_port_from_config("GitHTTP")
-    env = os.environ.copy()
-    env.pop('ARVADOS_API_TOKEN', None)
-    logf = open(_logfilename('githttpd'), WRITE_MODE)
-    agh = subprocess.Popen(['arvados-server', 'git-httpd'],
-        env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf)
-    _detachedSubprocesses.append(agh)
-    with open(_pidfile('githttpd'), 'w') as f:
-        f.write(str(agh.pid))
-    _wait_until_port_listens(gitport)
-
-def stop_arv_git_httpd():
-    if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
-        return
-    kill_server_pid(_pidfile('githttpd'))
-
 def run_keep_web():
     if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
         return
@@ -644,8 +623,6 @@ def run_nginx():
     nginxconf['KEEPWEBSSLPORT'] = external_port_from_config("WebDAV")
     nginxconf['KEEPPROXYPORT'] = internal_port_from_config("Keepproxy")
     nginxconf['KEEPPROXYSSLPORT'] = external_port_from_config("Keepproxy")
-    nginxconf['GITPORT'] = internal_port_from_config("GitHTTP")
-    nginxconf['GITSSLPORT'] = external_port_from_config("GitHTTP")
     nginxconf['HEALTHPORT'] = internal_port_from_config("Health")
     nginxconf['HEALTHSSLPORT'] = external_port_from_config("Health")
     nginxconf['WSPORT'] = internal_port_from_config("Websocket")
@@ -688,8 +665,6 @@ def setup_config():
     workbench1_external_port = find_available_port()
     workbench2_port = find_available_port()
     workbench2_external_port = find_available_port()
-    git_httpd_port = find_available_port()
-    git_httpd_external_port = find_available_port()
     health_httpd_port = find_available_port()
     health_httpd_external_port = find_available_port()
     keepproxy_port = find_available_port()
@@ -743,12 +718,6 @@ def setup_config():
                 "http://%s:%s"%(localhost, workbench2_port): {},
             },
         },
-        "GitHTTP": {
-            "ExternalURL": "https://%s:%s" % (localhost, git_httpd_external_port),
-            "InternalURLs": {
-                "http://%s:%s"%(localhost, git_httpd_port): {}
-            },
-        },
         "Health": {
             "ExternalURL": "https://%s:%s" % (localhost, health_httpd_external_port),
             "InternalURLs": {
@@ -821,13 +790,7 @@ def setup_config():
                     "ForwardSlashNameSubstitution": "/",
                     "TrashSweepInterval": "-1s",
                 },
-                "Git": {
-                    "Repositories": os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'git', 'test'),
-                },
                 "Containers": {
-                    "JobsAPI": {
-                        "GitInternalDir": os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'internal.git'),
-                    },
                     "LocalKeepBlobBuffersPerVCPU": 0,
                     "Logging": {
                         "SweepInterval": 0, # disable, otherwise test cases can't acquire dblock
@@ -959,7 +922,6 @@ if __name__ == "__main__":
         'start_keep', 'stop_keep',
         'start_keep_proxy', 'stop_keep_proxy',
         'start_keep-web', 'stop_keep-web',
-        'start_githttpd', 'stop_githttpd',
         'start_nginx', 'stop_nginx', 'setup_config',
     ]
     parser = argparse.ArgumentParser()
@@ -1007,10 +969,6 @@ if __name__ == "__main__":
         run_keep_proxy()
     elif args.action == 'stop_keep_proxy':
         stop_keep_proxy()
-    elif args.action == 'start_githttpd':
-        run_arv_git_httpd()
-    elif args.action == 'stop_githttpd':
-        stop_arv_git_httpd()
     elif args.action == 'start_keep-web':
         run_keep_web()
     elif args.action == 'stop_keep-web':
diff --git a/services/api/app/controllers/arvados/v1/schema_controller.rb b/services/api/app/controllers/arvados/v1/schema_controller.rb
index 8607325aca..ca803fd386 100644
--- a/services/api/app/controllers/arvados/v1/schema_controller.rb
+++ b/services/api/app/controllers/arvados/v1/schema_controller.rb
@@ -72,7 +72,6 @@ class Arvados::V1::SchemaController < ApplicationController
       workbenchUrl: Rails.configuration.Services.Workbench1.ExternalURL.to_s,
       workbench2Url: Rails.configuration.Services.Workbench2.ExternalURL.to_s,
       keepWebServiceUrl: Rails.configuration.Services.WebDAV.ExternalURL.to_s,
-      gitUrl: Rails.configuration.Services.GitHTTP.ExternalURL.to_s,
       parameters: {
         alt: {
           type: "string",
diff --git a/services/api/config/arvados_config.rb b/services/api/config/arvados_config.rb
index 594e247a36..f514fee641 100644
--- a/services/api/config/arvados_config.rb
+++ b/services/api/config/arvados_config.rb
@@ -84,7 +84,6 @@ arvcfg = ConfigLoader.new
 arvcfg.declare_config "ClusterID", NonemptyString, :uuid_prefix
 arvcfg.declare_config "ManagementToken", String, :ManagementToken
 arvcfg.declare_config "SystemRootToken", String
-arvcfg.declare_config "Git.Repositories", String, :git_repositories_dir
 arvcfg.declare_config "API.DisabledAPIs", Hash, :disable_api_methods, ->(cfg, k, v) { arrayToHash cfg, "API.DisabledAPIs", v }
 arvcfg.declare_config "API.MaxRequestSize", Integer, :max_request_size
 arvcfg.declare_config "API.MaxIndexDatabaseRead", Integer, :max_index_database_read
@@ -94,7 +93,6 @@ arvcfg.declare_config "API.RequestTimeout", ActiveSupport::Duration
 arvcfg.declare_config "API.AsyncPermissionsUpdateInterval", ActiveSupport::Duration, :async_permissions_update_interval
 arvcfg.declare_config "Users.AutoSetupNewUsers", Boolean, :auto_setup_new_users
 arvcfg.declare_config "Users.AutoSetupNewUsersWithVmUUID", String, :auto_setup_new_users_with_vm_uuid
-arvcfg.declare_config "Users.AutoSetupNewUsersWithRepository", Boolean, :auto_setup_new_users_with_repository
 arvcfg.declare_config "Users.AutoSetupUsernameBlacklist", Hash, :auto_setup_name_blacklist, ->(cfg, k, v) { arrayToHash cfg, "Users.AutoSetupUsernameBlacklist", v }
 arvcfg.declare_config "Users.NewUsersAreActive", Boolean, :new_users_are_active
 arvcfg.declare_config "Users.AutoAdminUserWithEmail", String, :auto_admin_user
@@ -148,8 +146,6 @@ arvcfg.declare_config "Services.Controller.ExternalURL", URI
 arvcfg.declare_config "Services.Workbench1.ExternalURL", URI, :workbench_address
 arvcfg.declare_config "Services.Websocket.ExternalURL", URI, :websocket_address
 arvcfg.declare_config "Services.WebDAV.ExternalURL", URI, :keep_web_service_url
-arvcfg.declare_config "Services.GitHTTP.ExternalURL", URI, :git_repo_https_base
-arvcfg.declare_config "Services.GitSSH.ExternalURL", URI, :git_repo_ssh_base, ->(cfg, k, v) { ConfigLoader.set_cfg cfg, "Services.GitSSH.ExternalURL", "ssh://#{v}" }
 arvcfg.declare_config "RemoteClusters", Hash, :remote_hosts, ->(cfg, k, v) {
   h = if cfg["RemoteClusters"] then
         cfg["RemoteClusters"].deep_dup
diff --git a/services/api/script/arvados-git-sync.rb b/services/api/script/arvados-git-sync.rb
deleted file mode 100755
index 9f8f050c10..0000000000
--- a/services/api/script/arvados-git-sync.rb
+++ /dev/null
@@ -1,271 +0,0 @@
-#!/usr/bin/env ruby
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'rubygems'
-require 'pp'
-require 'arvados'
-require 'tempfile'
-require 'yaml'
-require 'fileutils'
-
-# This script does the actual gitolite config management on disk.
-#
-# Ward Vandewege <ward at curii.com>
-
-# Default is development
-production = ARGV[0] == "production"
-
-ENV["RAILS_ENV"] = "development"
-ENV["RAILS_ENV"] = "production" if production
-
-DEBUG = 1
-
-# load and merge in the environment-specific application config info
-# if present, overriding base config parameters as specified
-path = File.absolute_path('../../config/arvados-clients.yml', __FILE__)
-if File.exist?(path) then
-  cp_config = File.open(path) do |f|
-    YAML.safe_load(f, filename: path)[ENV['RAILS_ENV']]
-  end
-else
-  puts "Please create a\n #{path}\n file"
-  exit 1
-end
-
-gitolite_url = cp_config['gitolite_url']
-gitolite_arvados_git_user_key = cp_config['gitolite_arvados_git_user_key']
-
-gitolite_tmpdir = cp_config['gitolite_tmp']
-gitolite_admin = File.join(gitolite_tmpdir, 'gitolite-admin')
-gitolite_admin_keydir = File.join(gitolite_admin, 'keydir')
-gitolite_keydir = File.join(gitolite_admin, 'keydir', 'arvados')
-
-ENV['ARVADOS_API_HOST'] = cp_config['arvados_api_host']
-ENV['ARVADOS_API_TOKEN'] = cp_config['arvados_api_token']
-if cp_config['arvados_api_host_insecure']
-  ENV['ARVADOS_API_HOST_INSECURE'] = 'true'
-else
-  ENV.delete('ARVADOS_API_HOST_INSECURE')
-end
-
-def ensure_directory(path, mode)
-  begin
-    Dir.mkdir(path, mode)
-  rescue Errno::EEXIST
-  end
-end
-
-def replace_file(path, contents)
-  unlink_now = true
-  dirname, basename = File.split(path)
-  FileUtils.mkpath(dirname)
-  new_file = Tempfile.new([basename, ".tmp"], dirname)
-  begin
-    new_file.write(contents)
-    new_file.flush
-    File.rename(new_file, path)
-    unlink_now = false
-  ensure
-    new_file.close(unlink_now)
-  end
-end
-
-def file_has_contents?(path, contents)
-  begin
-    IO.read(path) == contents
-  rescue Errno::ENOENT
-    false
-  end
-end
-
-module TrackCommitState
-  module ClassMethods
-    # Note that all classes that include TrackCommitState will have
-    # @@need_commit = true if any of them set it.  Since this flag reports
-    # a boolean state of the underlying git repository, that's OK in the
-    # current implementation.
-    @@need_commit = false
-
-    def changed?
-      @@need_commit
-    end
-
-    def ensure_in_git(path, contents)
-      unless file_has_contents?(path, contents)
-        replace_file(path, contents)
-        system("git", "add", path)
-        @@need_commit = true
-      end
-    end
-  end
-
-  def ensure_in_git(path, contents)
-    self.class.ensure_in_git(path, contents)
-  end
-
-  def self.included(base)
-    base.extend(ClassMethods)
-  end
-end
-
-class UserSSHKeys
-  include TrackCommitState
-
-  def initialize(user_keys_map, key_dir)
-    @user_keys_map = user_keys_map
-    @key_dir = key_dir
-    @installed = {}
-  end
-
-  def install(filename, pubkey)
-    unless pubkey.nil?
-      key_path = File.join(@key_dir, filename)
-      ensure_in_git(key_path, pubkey)
-    end
-    @installed[filename] = true
-  end
-
-  def ensure_keys_for_user(user_uuid)
-    return unless key_list = @user_keys_map.delete(user_uuid)
-    key_list.map { |k| k[:public_key] }.compact.each_with_index do |pubkey, ii|
-      # Handle putty-style ssh public keys
-      pubkey.sub!(/^(Comment: "r[^\n]*\n)(.*)$/m,'ssh-rsa \2 \1')
-      pubkey.sub!(/^(Comment: "d[^\n]*\n)(.*)$/m,'ssh-dss \2 \1')
-      pubkey.gsub!(/\n/,'')
-      pubkey.strip!
-      install("#{user_uuid}@#{ii}.pub", pubkey)
-    end
-  end
-
-  def installed?(filename)
-    @installed[filename]
-  end
-end
-
-class Repository
-  include TrackCommitState
-
-  @@aliases = {}
-
-  def initialize(arv_repo, user_keys)
-    @arv_repo = arv_repo
-    @user_keys = user_keys
-  end
-
-  def self.ensure_system_config(conf_root)
-    ensure_in_git(File.join(conf_root, "conf", "gitolite.conf"),
-                  %Q{include "auto/*.conf"\ninclude "admin/*.conf"\n})
-    ensure_in_git(File.join(conf_root, "arvadosaliases.pl"), alias_config)
-
-    conf_path = File.join(conf_root, "conf", "admin", "arvados.conf")
-    conf_file = %Q{
- at arvados_git_user = arvados_git_user
-
-repo gitolite-admin
-     RW           = @arvados_git_user
-
-}
-    ensure_directory(File.dirname(conf_path), 0755)
-    ensure_in_git(conf_path, conf_file)
-  end
-
-  def ensure_config(conf_root)
-    if name and (File.exist?(auto_conf_path(conf_root, name)))
-      # This gitolite installation knows the repository by name, rather than
-      # UUID.  Leave it configured that way until a separate migration is run.
-      basename = name
-    else
-      basename = uuid
-      @@aliases[name] = uuid unless name.nil?
-    end
-    conf_file = "\nrepo #{basename}\n"
-    @arv_repo[:user_permissions].sort.each do |user_uuid, perm|
-      conf_file += "\t#{perm[:gitolite_permissions]}\t= #{user_uuid}\n"
-      @user_keys.ensure_keys_for_user(user_uuid)
-    end
-    ensure_in_git(auto_conf_path(conf_root, basename), conf_file)
-  end
-
-  private
-
-  def auto_conf_path(conf_root, basename)
-    File.join(conf_root, "conf", "auto", "#{basename}.conf")
-  end
-
-  def uuid
-    @arv_repo[:uuid]
-  end
-
-  def name
-    if @arv_repo[:name].nil?
-      nil
-    else
-      @clean_name ||=
-        @arv_repo[:name].sub(/^[^A-Za-z]+/, "").gsub(/[^\w\.\/]/, "")
-    end
-  end
-
-  def self.alias_config
-    conf_s = "{\n"
-    @@aliases.sort.each do |(repo_name, repo_uuid)|
-      conf_s += "\t'#{repo_name}' \t=> '#{repo_uuid}',\n"
-    end
-    conf_s += "};\n"
-    conf_s
-  end
-end
-
-begin
-  # Get our local gitolite-admin repo up to snuff
-  if not File.exist?(gitolite_admin) then
-    ensure_directory(gitolite_tmpdir, 0700)
-    Dir.chdir(gitolite_tmpdir)
-    `git clone #{gitolite_url}`
-    Dir.chdir(gitolite_admin)
-  else
-    Dir.chdir(gitolite_admin)
-    `git pull`
-  end
-
-  arv = Arvados.new
-  permissions = arv.repository.get_all_permissions
-
-  ensure_directory(gitolite_keydir, 0700)
-  admin_user_ssh_keys = UserSSHKeys.new(permissions[:user_keys], gitolite_admin_keydir)
-  # Make sure the arvados_git_user key is installed; put it in gitolite_admin_keydir
-  # because that is where gitolite will try to put it if we do not.
-  admin_user_ssh_keys.install('arvados_git_user.pub', gitolite_arvados_git_user_key)
-
-  user_ssh_keys = UserSSHKeys.new(permissions[:user_keys], gitolite_keydir)
-  permissions[:repositories].each do |repo_record|
-    repo = Repository.new(repo_record, user_ssh_keys)
-    repo.ensure_config(gitolite_admin)
-  end
-  Repository.ensure_system_config(gitolite_admin)
-
-  # Clean up public key files that should not be present
-  Dir.chdir(gitolite_keydir)
-  stale_keys = Dir.glob('*.pub').reject do |key_file|
-    user_ssh_keys.installed?(key_file)
-  end
-  if stale_keys.any?
-    stale_keys.each { |key_file| puts "Extra file #{key_file}" }
-    system("git", "rm", "--quiet", *stale_keys)
-  end
-
-  if UserSSHKeys.changed? or Repository.changed? or stale_keys.any?
-    message = "#{Time.now().to_s}: update from API"
-    Dir.chdir(gitolite_admin)
-    `git add --all`
-    `git commit -m '#{message}'`
-    `git push`
-  end
-
-rescue => bang
-  puts "Error: " + bang.to_s
-  puts bang.backtrace.join("\n")
-  exit 1
-end
-
diff --git a/services/api/script/migrate-gitolite-to-uuid-storage.rb b/services/api/script/migrate-gitolite-to-uuid-storage.rb
deleted file mode 100755
index 98f25ca537..0000000000
--- a/services/api/script/migrate-gitolite-to-uuid-storage.rb
+++ /dev/null
@@ -1,226 +0,0 @@
-#!/usr/bin/env ruby
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-#
-# Prior to April 2015, Arvados Gitolite integration stored repositories by
-# name.  To improve user repository management, we switched to storing
-# repositories by UUID, and aliasing them to names.  This makes it easy to
-# have rich name hierarchies, and allow users to rename repositories.
-#
-# This script will migrate a name-based Gitolite configuration to a UUID-based
-# one.  To use it:
-#
-# 1. Change the value of REPOS_DIR below, if needed.
-# 2. Install this script in the same directory as `update-gitolite.rb`.
-# 3. Ensure that no *other* users can access Gitolite: edit gitolite's
-#    authorized_keys file so it only contains the arvados_git_user key,
-#    and disable the update-gitolite cron job.
-# 4. Run this script: `ruby migrate-gitolite-to-uuid-storage.rb production`.
-# 5. Undo step 3.
-
-require 'rubygems'
-require 'pp'
-require 'arvados'
-require 'tempfile'
-require 'yaml'
-
-REPOS_DIR = "/var/lib/gitolite/repositories"
-
-# Default is development
-production = ARGV[0] == "production"
-
-ENV["RAILS_ENV"] = "development"
-ENV["RAILS_ENV"] = "production" if production
-
-DEBUG = 1
-
-# load and merge in the environment-specific application config info
-# if present, overriding base config parameters as specified
-path = File.dirname(__FILE__) + '/config/arvados-clients.yml'
-if File.exist?(path) then
-  cp_config = File.open(path) do |f|
-    YAML.safe_load(f, filename: path)[ENV['RAILS_ENV']]
-  end
-else
-  puts "Please create a\n " + File.dirname(__FILE__) + "/config/arvados-clients.yml\n file"
-  exit 1
-end
-
-gitolite_url = cp_config['gitolite_url']
-gitolite_arvados_git_user_key = cp_config['gitolite_arvados_git_user_key']
-
-gitolite_tmpdir = File.join(File.absolute_path(File.dirname(__FILE__)),
-                            cp_config['gitolite_tmp'])
-gitolite_admin = File.join(gitolite_tmpdir, 'gitolite-admin')
-gitolite_keydir = File.join(gitolite_admin, 'keydir', 'arvados')
-
-ENV['ARVADOS_API_HOST'] = cp_config['arvados_api_host']
-ENV['ARVADOS_API_TOKEN'] = cp_config['arvados_api_token']
-if cp_config['arvados_api_host_insecure']
-  ENV['ARVADOS_API_HOST_INSECURE'] = 'true'
-else
-  ENV.delete('ARVADOS_API_HOST_INSECURE')
-end
-
-def ensure_directory(path, mode)
-  begin
-    Dir.mkdir(path, mode)
-  rescue Errno::EEXIST
-  end
-end
-
-def replace_file(path, contents)
-  unlink_now = true
-  dirname, basename = File.split(path)
-  new_file = Tempfile.new([basename, ".tmp"], dirname)
-  begin
-    new_file.write(contents)
-    new_file.flush
-    File.rename(new_file, path)
-    unlink_now = false
-  ensure
-    new_file.close(unlink_now)
-  end
-end
-
-def file_has_contents?(path, contents)
-  begin
-    IO.read(path) == contents
-  rescue Errno::ENOENT
-    false
-  end
-end
-
-module TrackCommitState
-  module ClassMethods
-    # Note that all classes that include TrackCommitState will have
-    # @@need_commit = true if any of them set it.  Since this flag reports
-    # a boolean state of the underlying git repository, that's OK in the
-    # current implementation.
-    @@need_commit = false
-
-    def changed?
-      @@need_commit
-    end
-
-    def ensure_in_git(path, contents)
-      unless file_has_contents?(path, contents)
-        replace_file(path, contents)
-        system("git", "add", path)
-        @@need_commit = true
-      end
-    end
-  end
-
-  def ensure_in_git(path, contents)
-    self.class.ensure_in_git(path, contents)
-  end
-
-  def self.included(base)
-    base.extend(ClassMethods)
-  end
-end
-
-class Repository
-  include TrackCommitState
-
-  @@aliases = {}
-
-  def initialize(arv_repo)
-    @arv_repo = arv_repo
-  end
-
-  def self.ensure_system_config(conf_root)
-    ensure_in_git(File.join(conf_root, "arvadosaliases.pl"), alias_config)
-  end
-
-  def self.rename_repos(repos_root)
-    @@aliases.each_pair do |uuid, name|
-      begin
-        File.rename(File.join(repos_root, "#{name}.git/"),
-                    File.join(repos_root, "#{uuid}.git"))
-      rescue Errno::ENOENT
-      end
-      if name == "arvados"
-        Dir.chdir(repos_root) { File.symlink("#{uuid}.git/", "arvados.git") }
-      end
-    end
-  end
-
-  def ensure_config(conf_root)
-    return if name.nil?
-    @@aliases[uuid] = name
-    name_conf_path = auto_conf_path(conf_root, name)
-    return unless File.exist?(name_conf_path)
-    conf_file = IO.read(name_conf_path)
-    conf_file.gsub!(/^repo #{Regexp.escape(name)}$/m, "repo #{uuid}")
-    ensure_in_git(auto_conf_path(conf_root, uuid), conf_file)
-    File.unlink(name_conf_path)
-    system("git", "rm", "--quiet", name_conf_path)
-  end
-
-  private
-
-  def auto_conf_path(conf_root, basename)
-    File.join(conf_root, "conf", "auto", "#{basename}.conf")
-  end
-
-  def uuid
-    @arv_repo[:uuid]
-  end
-
-  def name
-    if @arv_repo[:name].nil?
-      nil
-    else
-      @clean_name ||=
-        @arv_repo[:name].sub(/^[^A-Za-z]+/, "").gsub(/[^\w\.\/]/, "")
-    end
-  end
-
-  def self.alias_config
-    conf_s = "{\n"
-    @@aliases.sort.each do |(repo_name, repo_uuid)|
-      conf_s += "\t'#{repo_name}' \t=> '#{repo_uuid}',\n"
-    end
-    conf_s += "};\n"
-    conf_s
-  end
-end
-
-begin
-  # Get our local gitolite-admin repo up to snuff
-  if not File.exist?(gitolite_admin) then
-    ensure_directory(gitolite_tmpdir, 0700)
-    Dir.chdir(gitolite_tmpdir)
-    `git clone #{gitolite_url}`
-    Dir.chdir(gitolite_admin)
-  else
-    Dir.chdir(gitolite_admin)
-    `git pull`
-  end
-
-  arv = Arvados.new
-  permissions = arv.repository.get_all_permissions
-
-  permissions[:repositories].each do |repo_record|
-    repo = Repository.new(repo_record)
-    repo.ensure_config(gitolite_admin)
-  end
-  Repository.ensure_system_config(gitolite_admin)
-
-  message = "#{Time.now().to_s}: migrate to storing repositories by UUID"
-  Dir.chdir(gitolite_admin)
-  `git add --all`
-  `git commit -m '#{message}'`
-  Repository.rename_repos(REPOS_DIR)
-  `git push`
-
-rescue => bang
-  puts "Error: " + bang.to_s
-  puts bang.backtrace.join("\n")
-  exit 1
-end
-
diff --git a/services/githttpd/auth_handler.go b/services/githttpd/auth_handler.go
deleted file mode 100644
index c6b23fd4c8..0000000000
--- a/services/githttpd/auth_handler.go
+++ /dev/null
@@ -1,211 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-package githttpd
-
-import (
-	"errors"
-	"log"
-	"net/http"
-	"os"
-	"regexp"
-	"strings"
-	"time"
-
-	"git.arvados.org/arvados.git/sdk/go/arvados"
-	"git.arvados.org/arvados.git/sdk/go/arvadosclient"
-	"git.arvados.org/arvados.git/sdk/go/auth"
-	"git.arvados.org/arvados.git/sdk/go/httpserver"
-	"github.com/sirupsen/logrus"
-)
-
-type authHandler struct {
-	handler    http.Handler
-	clientPool *arvadosclient.ClientPool
-	cluster    *arvados.Cluster
-}
-
-func (h *authHandler) CheckHealth() error {
-	return nil
-}
-
-func (h *authHandler) Done() <-chan struct{} {
-	return nil
-}
-
-func (h *authHandler) ServeHTTP(wOrig http.ResponseWriter, r *http.Request) {
-	var statusCode int
-	var statusText string
-	var apiToken string
-
-	w := httpserver.WrapResponseWriter(wOrig)
-
-	if r.Method == "OPTIONS" {
-		method := r.Header.Get("Access-Control-Request-Method")
-		if method != "GET" && method != "POST" {
-			w.WriteHeader(http.StatusMethodNotAllowed)
-			return
-		}
-		w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type")
-		w.Header().Set("Access-Control-Allow-Methods", "GET, POST")
-		w.Header().Set("Access-Control-Allow-Origin", "*")
-		w.Header().Set("Access-Control-Max-Age", "86400")
-		w.WriteHeader(http.StatusOK)
-		return
-	}
-
-	if r.Header.Get("Origin") != "" {
-		// Allow simple cross-origin requests without user
-		// credentials ("user credentials" as defined by CORS,
-		// i.e., cookies, HTTP authentication, and client-side
-		// SSL certificates. See
-		// http://www.w3.org/TR/cors/#user-credentials).
-		w.Header().Set("Access-Control-Allow-Origin", "*")
-	}
-
-	defer func() {
-		if w.WroteStatus() == 0 {
-			// Nobody has called WriteHeader yet: that
-			// must be our job.
-			w.WriteHeader(statusCode)
-			if statusCode >= 400 {
-				w.Write([]byte(statusText))
-			}
-		}
-	}()
-
-	creds := auth.CredentialsFromRequest(r)
-	if len(creds.Tokens) == 0 {
-		statusCode, statusText = http.StatusUnauthorized, "no credentials provided"
-		w.Header().Add("WWW-Authenticate", "Basic realm=\"git\"")
-		return
-	}
-	apiToken = creds.Tokens[0]
-
-	// Access to paths "/foo/bar.git/*" and "/foo/bar/.git/*" are
-	// protected by the permissions on the repository named
-	// "foo/bar".
-	pathParts := strings.SplitN(r.URL.Path[1:], ".git/", 2)
-	if len(pathParts) != 2 {
-		statusCode, statusText = http.StatusNotFound, "not found"
-		return
-	}
-	repoName := pathParts[0]
-	repoName = strings.TrimRight(repoName, "/")
-	httpserver.SetResponseLogFields(r.Context(), logrus.Fields{
-		"repoName": repoName,
-	})
-
-	arv := h.clientPool.Get()
-	if arv == nil {
-		statusCode, statusText = http.StatusInternalServerError, "connection pool failed: "+h.clientPool.Err().Error()
-		return
-	}
-	defer h.clientPool.Put(arv)
-
-	// Log the UUID if the supplied token is a v2 token, otherwise
-	// just the last five characters.
-	httpserver.SetResponseLogFields(r.Context(), logrus.Fields{
-		"tokenUUID": func() string {
-			if strings.HasPrefix(apiToken, "v2/") && strings.IndexRune(apiToken[3:], '/') == 27 {
-				// UUID part of v2 token
-				return apiToken[3:30]
-			} else if len(apiToken) > 5 {
-				return "[...]" + apiToken[len(apiToken)-5:]
-			} else {
-				return apiToken
-			}
-		}(),
-	})
-
-	// Ask API server whether the repository is readable using
-	// this token (by trying to read it!)
-	arv.ApiToken = apiToken
-	repoUUID, err := h.lookupRepo(arv, repoName)
-	if err != nil {
-		statusCode, statusText = http.StatusInternalServerError, err.Error()
-		return
-	}
-	if repoUUID == "" {
-		statusCode, statusText = http.StatusNotFound, "not found"
-		return
-	}
-
-	isWrite := strings.HasSuffix(r.URL.Path, "/git-receive-pack")
-	if !isWrite {
-		statusText = "read"
-	} else {
-		err := arv.Update("repositories", repoUUID, arvadosclient.Dict{
-			"repository": arvadosclient.Dict{
-				"modified_at": time.Now().String(),
-			},
-		}, &arvadosclient.Dict{})
-		if err != nil {
-			statusCode, statusText = http.StatusForbidden, err.Error()
-			return
-		}
-		statusText = "write"
-	}
-
-	// Regardless of whether the client asked for "/foo.git" or
-	// "/foo/.git", we choose whichever variant exists in our repo
-	// root, and we try {uuid}.git and {uuid}/.git first. If none
-	// of these exist, we 404 even though the API told us the repo
-	// _should_ exist (presumably this means the repo was just
-	// created, and gitolite sync hasn't run yet).
-	rewrittenPath := ""
-	tryDirs := []string{
-		"/" + repoUUID + ".git",
-		"/" + repoUUID + "/.git",
-		"/" + repoName + ".git",
-		"/" + repoName + "/.git",
-	}
-	for _, dir := range tryDirs {
-		if fileInfo, err := os.Stat(h.cluster.Git.Repositories + dir); err != nil {
-			if !os.IsNotExist(err) {
-				statusCode, statusText = http.StatusInternalServerError, err.Error()
-				return
-			}
-		} else if fileInfo.IsDir() {
-			rewrittenPath = dir + "/" + pathParts[1]
-			break
-		}
-	}
-	if rewrittenPath == "" {
-		log.Println("WARNING:", repoUUID,
-			"git directory not found in", h.cluster.Git.Repositories, tryDirs)
-		// We say "content not found" to disambiguate from the
-		// earlier "API says that repo does not exist" error.
-		statusCode, statusText = http.StatusNotFound, "content not found"
-		return
-	}
-	r.URL.Path = rewrittenPath
-
-	h.handler.ServeHTTP(w, r)
-}
-
-var uuidRegexp = regexp.MustCompile(`^[0-9a-z]{5}-s0uqq-[0-9a-z]{15}$`)
-
-func (h *authHandler) lookupRepo(arv *arvadosclient.ArvadosClient, repoName string) (string, error) {
-	reposFound := arvadosclient.Dict{}
-	var column string
-	if uuidRegexp.MatchString(repoName) {
-		column = "uuid"
-	} else {
-		column = "name"
-	}
-	err := arv.List("repositories", arvadosclient.Dict{
-		"filters": [][]string{{column, "=", repoName}},
-	}, &reposFound)
-	if err != nil {
-		return "", err
-	} else if avail, ok := reposFound["items_available"].(float64); !ok {
-		return "", errors.New("bad list response from API")
-	} else if avail < 1 {
-		return "", nil
-	} else if avail > 1 {
-		return "", errors.New("name collision")
-	}
-	return reposFound["items"].([]interface{})[0].(map[string]interface{})["uuid"].(string), nil
-}
diff --git a/services/githttpd/auth_handler_test.go b/services/githttpd/auth_handler_test.go
deleted file mode 100644
index 2d1ec966a4..0000000000
--- a/services/githttpd/auth_handler_test.go
+++ /dev/null
@@ -1,175 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-package githttpd
-
-import (
-	"io"
-	"log"
-	"net/http"
-	"net/http/httptest"
-	"net/url"
-	"path/filepath"
-	"strings"
-
-	"git.arvados.org/arvados.git/lib/config"
-	"git.arvados.org/arvados.git/sdk/go/arvados"
-	"git.arvados.org/arvados.git/sdk/go/arvadosclient"
-	"git.arvados.org/arvados.git/sdk/go/arvadostest"
-	"git.arvados.org/arvados.git/sdk/go/ctxlog"
-	check "gopkg.in/check.v1"
-)
-
-var _ = check.Suite(&AuthHandlerSuite{})
-
-type AuthHandlerSuite struct {
-	cluster *arvados.Cluster
-}
-
-func (s *AuthHandlerSuite) SetUpTest(c *check.C) {
-	arvadostest.ResetEnv()
-	repoRoot, err := filepath.Abs("../api/tmp/git/test")
-	c.Assert(err, check.IsNil)
-
-	cfg, err := config.NewLoader(nil, ctxlog.TestLogger(c)).Load()
-	c.Assert(err, check.Equals, nil)
-	s.cluster, err = cfg.GetCluster("")
-	c.Assert(err, check.Equals, nil)
-
-	s.cluster.Services.GitHTTP.InternalURLs = map[arvados.URL]arvados.ServiceInstance{{Host: "localhost:0"}: {}}
-	s.cluster.TLS.Insecure = true
-	s.cluster.Git.GitCommand = "/usr/bin/git"
-	s.cluster.Git.Repositories = repoRoot
-}
-
-func (s *AuthHandlerSuite) TestPermission(c *check.C) {
-	client, err := arvados.NewClientFromConfig(s.cluster)
-	c.Assert(err, check.IsNil)
-	ac, err := arvadosclient.New(client)
-	c.Assert(err, check.IsNil)
-	h := &authHandler{
-		cluster:    s.cluster,
-		clientPool: &arvadosclient.ClientPool{Prototype: ac},
-		handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-			log.Printf("%v", r.URL)
-			io.WriteString(w, r.URL.Path)
-		}),
-	}
-	baseURL, err := url.Parse("http://git.example/")
-	c.Assert(err, check.IsNil)
-	for _, trial := range []struct {
-		label   string
-		token   string
-		pathIn  string
-		pathOut string
-		status  int
-	}{
-		{
-			label:   "read repo by name",
-			token:   arvadostest.ActiveToken,
-			pathIn:  arvadostest.Repository2Name + ".git/git-upload-pack",
-			pathOut: arvadostest.Repository2UUID + ".git/git-upload-pack",
-		},
-		{
-			label:   "read repo by uuid",
-			token:   arvadostest.ActiveToken,
-			pathIn:  arvadostest.Repository2UUID + ".git/git-upload-pack",
-			pathOut: arvadostest.Repository2UUID + ".git/git-upload-pack",
-		},
-		{
-			label:   "write repo by name",
-			token:   arvadostest.ActiveToken,
-			pathIn:  arvadostest.Repository2Name + ".git/git-receive-pack",
-			pathOut: arvadostest.Repository2UUID + ".git/git-receive-pack",
-		},
-		{
-			label:   "write repo by uuid",
-			token:   arvadostest.ActiveToken,
-			pathIn:  arvadostest.Repository2UUID + ".git/git-receive-pack",
-			pathOut: arvadostest.Repository2UUID + ".git/git-receive-pack",
-		},
-		{
-			label:  "uuid not found",
-			token:  arvadostest.ActiveToken,
-			pathIn: strings.Replace(arvadostest.Repository2UUID, "6", "z", -1) + ".git/git-upload-pack",
-			status: http.StatusNotFound,
-		},
-		{
-			label:  "name not found",
-			token:  arvadostest.ActiveToken,
-			pathIn: "nonexistent-bogus.git/git-upload-pack",
-			status: http.StatusNotFound,
-		},
-		{
-			label:   "read read-only repo",
-			token:   arvadostest.SpectatorToken,
-			pathIn:  arvadostest.FooRepoName + ".git/git-upload-pack",
-			pathOut: arvadostest.FooRepoUUID + "/.git/git-upload-pack",
-		},
-		{
-			label:  "write read-only repo",
-			token:  arvadostest.SpectatorToken,
-			pathIn: arvadostest.FooRepoName + ".git/git-receive-pack",
-			status: http.StatusForbidden,
-		},
-	} {
-		c.Logf("trial label: %q", trial.label)
-		u, err := baseURL.Parse(trial.pathIn)
-		c.Assert(err, check.IsNil)
-		resp := httptest.NewRecorder()
-		req := &http.Request{
-			Method: "POST",
-			URL:    u,
-			Header: http.Header{
-				"Authorization": {"Bearer " + trial.token}}}
-		h.ServeHTTP(resp, req)
-		if trial.status == 0 {
-			trial.status = http.StatusOK
-		}
-		c.Check(resp.Code, check.Equals, trial.status)
-		if trial.status < 400 {
-			if trial.pathOut != "" && !strings.HasPrefix(trial.pathOut, "/") {
-				trial.pathOut = "/" + trial.pathOut
-			}
-			c.Check(resp.Body.String(), check.Equals, trial.pathOut)
-		}
-	}
-}
-
-func (s *AuthHandlerSuite) TestCORS(c *check.C) {
-	h := &authHandler{cluster: s.cluster}
-
-	// CORS preflight
-	resp := httptest.NewRecorder()
-	req := &http.Request{
-		Method: "OPTIONS",
-		Header: http.Header{
-			"Origin":                        {"*"},
-			"Access-Control-Request-Method": {"GET"},
-		},
-	}
-	h.ServeHTTP(resp, req)
-	c.Check(resp.Code, check.Equals, http.StatusOK)
-	c.Check(resp.Header().Get("Access-Control-Allow-Methods"), check.Equals, "GET, POST")
-	c.Check(resp.Header().Get("Access-Control-Allow-Headers"), check.Equals, "Authorization, Content-Type")
-	c.Check(resp.Header().Get("Access-Control-Allow-Origin"), check.Equals, "*")
-	c.Check(resp.Body.String(), check.Equals, "")
-
-	// CORS actual request. Bogus token and path ensure
-	// authHandler responds 4xx without calling our wrapped (nil)
-	// handler.
-	u, err := url.Parse("git.zzzzz.arvadosapi.com/test")
-	c.Assert(err, check.Equals, nil)
-	resp = httptest.NewRecorder()
-	req = &http.Request{
-		Method: "GET",
-		URL:    u,
-		Header: http.Header{
-			"Origin":        {"*"},
-			"Authorization": {"OAuth2 foobar"},
-		},
-	}
-	h.ServeHTTP(resp, req)
-	c.Check(resp.Header().Get("Access-Control-Allow-Origin"), check.Equals, "*")
-}
diff --git a/services/githttpd/cmd.go b/services/githttpd/cmd.go
deleted file mode 100644
index e6ca3c0743..0000000000
--- a/services/githttpd/cmd.go
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-package githttpd
-
-import (
-	"context"
-
-	"git.arvados.org/arvados.git/lib/service"
-	"git.arvados.org/arvados.git/sdk/go/arvados"
-	"git.arvados.org/arvados.git/sdk/go/arvadosclient"
-	"github.com/prometheus/client_golang/prometheus"
-)
-
-var Command = service.Command(arvados.ServiceNameGitHTTP, newHandler)
-
-func newHandler(ctx context.Context, cluster *arvados.Cluster, token string, reg *prometheus.Registry) service.Handler {
-	client, err := arvados.NewClientFromConfig(cluster)
-	if err != nil {
-		return service.ErrorHandler(ctx, cluster, err)
-	}
-	ac, err := arvadosclient.New(client)
-	if err != nil {
-		return service.ErrorHandler(ctx, cluster, err)
-	}
-	return &authHandler{
-		clientPool: &arvadosclient.ClientPool{Prototype: ac},
-		cluster:    cluster,
-		handler:    newGitHandler(ctx, cluster),
-	}
-}
diff --git a/services/githttpd/git_handler.go b/services/githttpd/git_handler.go
deleted file mode 100644
index 7c94294c04..0000000000
--- a/services/githttpd/git_handler.go
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-package githttpd
-
-import (
-	"context"
-	"net"
-	"net/http"
-	"net/http/cgi"
-	"os"
-
-	"git.arvados.org/arvados.git/sdk/go/arvados"
-	"git.arvados.org/arvados.git/sdk/go/ctxlog"
-)
-
-// gitHandler is an http.Handler that invokes git-http-backend (or
-// whatever backend is configured) via CGI, with appropriate
-// environment variables in place for git-http-backend or
-// gitolite-shell.
-type gitHandler struct {
-	cgi.Handler
-}
-
-func newGitHandler(ctx context.Context, cluster *arvados.Cluster) http.Handler {
-	const glBypass = "GL_BYPASS_ACCESS_CHECKS"
-	const glHome = "GITOLITE_HTTP_HOME"
-	var env []string
-	path := os.Getenv("PATH")
-	if cluster.Git.GitoliteHome != "" {
-		env = append(env,
-			glHome+"="+cluster.Git.GitoliteHome,
-			glBypass+"=1")
-		path = path + ":" + cluster.Git.GitoliteHome + "/bin"
-	} else if home, bypass := os.Getenv(glHome), os.Getenv(glBypass); home != "" || bypass != "" {
-		env = append(env, glHome+"="+home, glBypass+"="+bypass)
-		ctxlog.FromContext(ctx).Printf("DEPRECATED: Passing through %s and %s environment variables. Use GitoliteHome configuration instead.", glHome, glBypass)
-	}
-
-	var listen arvados.URL
-	for listen = range cluster.Services.GitHTTP.InternalURLs {
-		break
-	}
-	env = append(env,
-		"GIT_PROJECT_ROOT="+cluster.Git.Repositories,
-		"GIT_HTTP_EXPORT_ALL=",
-		"SERVER_ADDR="+listen.Host,
-		"PATH="+path)
-	return &gitHandler{
-		Handler: cgi.Handler{
-			Path: cluster.Git.GitCommand,
-			Dir:  cluster.Git.Repositories,
-			Env:  env,
-			Args: []string{"http-backend"},
-		},
-	}
-}
-
-func (h *gitHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	remoteHost, remotePort, err := net.SplitHostPort(r.RemoteAddr)
-	if err != nil {
-		ctxlog.FromContext(r.Context()).Errorf("Internal error: SplitHostPort(r.RemoteAddr==%q): %s", r.RemoteAddr, err)
-		w.WriteHeader(http.StatusInternalServerError)
-		return
-	}
-
-	// Copy the wrapped cgi.Handler, so these request-specific
-	// variables don't leak into the next request.
-	handlerCopy := h.Handler
-	handlerCopy.Env = append(handlerCopy.Env,
-		// In Go1.5 we can skip this, net/http/cgi will do it for us:
-		"REMOTE_HOST="+remoteHost,
-		"REMOTE_ADDR="+remoteHost,
-		"REMOTE_PORT="+remotePort,
-		// Ideally this would be a real username:
-		"REMOTE_USER="+r.RemoteAddr,
-	)
-	handlerCopy.ServeHTTP(w, r)
-}
diff --git a/services/githttpd/git_handler_test.go b/services/githttpd/git_handler_test.go
deleted file mode 100644
index ef2ee28e79..0000000000
--- a/services/githttpd/git_handler_test.go
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-package githttpd
-
-import (
-	"context"
-	"net/http"
-	"net/http/httptest"
-	"net/url"
-	"regexp"
-
-	"git.arvados.org/arvados.git/lib/config"
-	"git.arvados.org/arvados.git/sdk/go/arvados"
-	"git.arvados.org/arvados.git/sdk/go/ctxlog"
-	check "gopkg.in/check.v1"
-)
-
-var _ = check.Suite(&GitHandlerSuite{})
-
-type GitHandlerSuite struct {
-	cluster *arvados.Cluster
-}
-
-func (s *GitHandlerSuite) SetUpTest(c *check.C) {
-	cfg, err := config.NewLoader(nil, ctxlog.TestLogger(c)).Load()
-	c.Assert(err, check.Equals, nil)
-	s.cluster, err = cfg.GetCluster("")
-	c.Assert(err, check.Equals, nil)
-
-	s.cluster.Services.GitHTTP.InternalURLs = map[arvados.URL]arvados.ServiceInstance{{Host: "localhost:80"}: {}}
-	s.cluster.Git.GitoliteHome = "/test/ghh"
-	s.cluster.Git.Repositories = "/"
-}
-
-func (s *GitHandlerSuite) TestEnvVars(c *check.C) {
-	u, err := url.Parse("git.zzzzz.arvadosapi.com/test")
-	c.Check(err, check.Equals, nil)
-	resp := httptest.NewRecorder()
-	req := &http.Request{
-		Method:     "GET",
-		URL:        u,
-		RemoteAddr: "[::1]:12345",
-	}
-	h := newGitHandler(context.Background(), s.cluster)
-	h.(*gitHandler).Path = "/bin/sh"
-	h.(*gitHandler).Args = []string{"-c", "printf 'Content-Type: text/plain\r\n\r\n'; env"}
-
-	h.ServeHTTP(resp, req)
-
-	c.Check(resp.Code, check.Equals, http.StatusOK)
-	body := resp.Body.String()
-	c.Check(body, check.Matches, `(?ms).*^PATH=.*:/test/ghh/bin$.*`)
-	c.Check(body, check.Matches, `(?ms).*^GITOLITE_HTTP_HOME=/test/ghh$.*`)
-	c.Check(body, check.Matches, `(?ms).*^GL_BYPASS_ACCESS_CHECKS=1$.*`)
-	c.Check(body, check.Matches, `(?ms).*^REMOTE_HOST=::1$.*`)
-	c.Check(body, check.Matches, `(?ms).*^REMOTE_PORT=12345$.*`)
-	c.Check(body, check.Matches, `(?ms).*^SERVER_ADDR=`+regexp.QuoteMeta("localhost:80")+`$.*`)
-}
-
-func (s *GitHandlerSuite) TestCGIErrorOnSplitHostPortError(c *check.C) {
-	u, err := url.Parse("git.zzzzz.arvadosapi.com/test")
-	c.Check(err, check.Equals, nil)
-	resp := httptest.NewRecorder()
-	req := &http.Request{
-		Method:     "GET",
-		URL:        u,
-		RemoteAddr: "test.bad.address.missing.port",
-	}
-	h := newGitHandler(context.Background(), s.cluster)
-	h.ServeHTTP(resp, req)
-	c.Check(resp.Code, check.Equals, http.StatusInternalServerError)
-	c.Check(resp.Body.String(), check.Equals, "")
-}
diff --git a/services/githttpd/gitolite_test.go b/services/githttpd/gitolite_test.go
deleted file mode 100644
index d34c413c1b..0000000000
--- a/services/githttpd/gitolite_test.go
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-package githttpd
-
-import (
-	"io/ioutil"
-	"os"
-	"os/exec"
-	"strings"
-
-	"git.arvados.org/arvados.git/lib/config"
-	"git.arvados.org/arvados.git/sdk/go/arvados"
-	"git.arvados.org/arvados.git/sdk/go/ctxlog"
-	check "gopkg.in/check.v1"
-)
-
-var _ = check.Suite(&GitoliteSuite{})
-
-// GitoliteSuite tests need an API server, an arvados-git-httpd
-// server, and a repository hosted by gitolite.
-type GitoliteSuite struct {
-	IntegrationSuite
-	gitoliteHome string
-}
-
-func (s *GitoliteSuite) SetUpTest(c *check.C) {
-	var err error
-	s.gitoliteHome, err = ioutil.TempDir("", "githttp")
-	c.Assert(err, check.Equals, nil)
-
-	runGitolite := func(prog string, args ...string) {
-		c.Log(prog, " ", args)
-		cmd := exec.Command(prog, args...)
-		cmd.Dir = 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)
-	}
-
-	runGitolite("gitolite", "setup", "--admin", "root")
-
-	s.tmpRepoRoot = s.gitoliteHome + "/repositories"
-
-	cfg, err := config.NewLoader(nil, ctxlog.TestLogger(c)).Load()
-	c.Assert(err, check.Equals, nil)
-	s.cluster, err = cfg.GetCluster("")
-	c.Assert(err, check.Equals, nil)
-
-	s.cluster.Services.GitHTTP.InternalURLs = map[arvados.URL]arvados.ServiceInstance{{Host: "localhost:0"}: {}}
-	s.cluster.TLS.Insecure = true
-	s.cluster.Git.GitCommand = "/usr/share/gitolite3/gitolite-shell"
-	s.cluster.Git.GitoliteHome = s.gitoliteHome
-	s.cluster.Git.Repositories = s.tmpRepoRoot
-
-	s.IntegrationSuite.SetUpTest(c)
-
-	// Install the gitolite hooks in the bare repo we made in
-	// (*IntegrationTest)SetUpTest() -- see 2.2.4 at
-	// http://gitolite.com/gitolite/gitolite.html
-	runGitolite("gitolite", "setup")
-}
-
-func (s *GitoliteSuite) TearDownTest(c *check.C) {
-	// We really want Unsetenv here, but it's not worth forcing an
-	// upgrade to Go 1.4.
-	os.Setenv("GITOLITE_HTTP_HOME", "")
-	os.Setenv("GL_BYPASS_ACCESS_CHECKS", "")
-	if s.gitoliteHome != "" {
-		err := os.RemoveAll(s.gitoliteHome)
-		c.Check(err, check.Equals, nil)
-	}
-	s.IntegrationSuite.TearDownTest(c)
-}
-
-func (s *GitoliteSuite) TestFetch(c *check.C) {
-	err := s.RunGit(c, activeToken, "fetch", "active/foo.git", "refs/heads/main")
-	c.Check(err, check.Equals, nil)
-}
-
-func (s *GitoliteSuite) TestFetchUnreadable(c *check.C) {
-	err := s.RunGit(c, anonymousToken, "fetch", "active/foo.git")
-	c.Check(err, check.ErrorMatches, `.* not found.*`)
-}
-
-func (s *GitoliteSuite) TestPush(c *check.C) {
-	err := s.RunGit(c, activeToken, "push", "active/foo.git", "main:gitolite-push")
-	c.Check(err, check.Equals, nil)
-
-	// 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",
-		"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()
-	c.Check(err, check.Equals, nil)
-	c.Logf("gitolite log message: %q", string(gitoliteLog))
-}
-
-func (s *GitoliteSuite) TestPushUnwritable(c *check.C) {
-	err := s.RunGit(c, spectatorToken, "push", "active/foo.git", "main:gitolite-push-fail")
-	c.Check(err, check.ErrorMatches, `.*HTTP (code = )?403.*`)
-}
diff --git a/services/githttpd/integration_test.go b/services/githttpd/integration_test.go
deleted file mode 100644
index c819272d3e..0000000000
--- a/services/githttpd/integration_test.go
+++ /dev/null
@@ -1,147 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-package githttpd
-
-import (
-	"context"
-	"errors"
-	"io/ioutil"
-	"os"
-	"os/exec"
-	"strings"
-	"testing"
-
-	"git.arvados.org/arvados.git/lib/config"
-	"git.arvados.org/arvados.git/sdk/go/arvados"
-	"git.arvados.org/arvados.git/sdk/go/arvadostest"
-	"git.arvados.org/arvados.git/sdk/go/ctxlog"
-	"git.arvados.org/arvados.git/sdk/go/httpserver"
-	check "gopkg.in/check.v1"
-)
-
-// Gocheck boilerplate
-func Test(t *testing.T) {
-	check.TestingT(t)
-}
-
-// IntegrationSuite tests need an API server and an arvados-git-httpd
-// server. See GitSuite and GitoliteSuite.
-type IntegrationSuite struct {
-	tmpRepoRoot string
-	tmpWorkdir  string
-	testServer  *httpserver.Server
-	cluster     *arvados.Cluster
-}
-
-func (s *IntegrationSuite) SetUpTest(c *check.C) {
-	arvadostest.ResetEnv()
-
-	var err error
-	if s.tmpRepoRoot == "" {
-		s.tmpRepoRoot, err = ioutil.TempDir("", "githttp")
-		c.Assert(err, check.Equals, nil)
-	}
-	s.tmpWorkdir, err = ioutil.TempDir("", "githttp")
-	c.Assert(err, check.Equals, nil)
-	_, err = exec.Command("git", "init", "--bare", s.tmpRepoRoot+"/zzzzz-s0uqq-382brsig8rp3666.git").Output()
-	c.Assert(err, check.Equals, nil)
-	// we need git 2.28 to specify the initial branch with -b; Buster only has 2.20; so we do it in 2 steps
-	_, err = exec.Command("git", "init", s.tmpWorkdir).Output()
-	c.Assert(err, check.Equals, nil)
-	_, err = exec.Command("sh", "-c", "cd "+s.tmpWorkdir+" && git checkout -b main").Output()
-	c.Assert(err, check.Equals, nil)
-	_, err = exec.Command("sh", "-c", "cd "+s.tmpWorkdir+" && echo initial >initial && git add initial && git -c user.name=Initial -c user.email=Initial commit -am 'foo: initial commit'").CombinedOutput()
-	c.Assert(err, check.Equals, nil)
-	_, err = exec.Command("sh", "-c", "cd "+s.tmpWorkdir+" && git push "+s.tmpRepoRoot+"/zzzzz-s0uqq-382brsig8rp3666.git main:main").CombinedOutput()
-	c.Assert(err, check.Equals, nil)
-	_, err = exec.Command("sh", "-c", "cd "+s.tmpWorkdir+" && echo work >work && git add work && git -c user.name=Foo -c user.email=Foo commit -am 'workdir: test'").CombinedOutput()
-	c.Assert(err, check.Equals, nil)
-
-	if s.cluster == nil {
-		cfg, err := config.NewLoader(nil, ctxlog.TestLogger(c)).Load()
-		c.Assert(err, check.Equals, nil)
-		s.cluster, err = cfg.GetCluster("")
-		c.Assert(err, check.Equals, nil)
-
-		s.cluster.Services.GitHTTP.InternalURLs = map[arvados.URL]arvados.ServiceInstance{{Host: "localhost:0"}: {}}
-		s.cluster.TLS.Insecure = true
-		s.cluster.Git.GitCommand = "/usr/bin/git"
-		s.cluster.Git.Repositories = s.tmpRepoRoot
-		s.cluster.ManagementToken = arvadostest.ManagementToken
-	}
-
-	s.testServer = &httpserver.Server{}
-	s.testServer.Handler = httpserver.LogRequests(newHandler(context.Background(), s.cluster, "", nil))
-	err = s.testServer.Start()
-	c.Assert(err, check.Equals, nil)
-
-	_, err = exec.Command("git", "config",
-		"--file", s.tmpWorkdir+"/.git/config",
-		"credential.http://"+s.testServer.Addr+"/.helper",
-		"!cred(){ cat >/dev/null; if [ \"$1\" = get ]; then echo password=$ARVADOS_API_TOKEN; fi; };cred").Output()
-	c.Assert(err, check.Equals, nil)
-	_, err = exec.Command("git", "config",
-		"--file", s.tmpWorkdir+"/.git/config",
-		"credential.http://"+s.testServer.Addr+"/.username",
-		"none").Output()
-	c.Assert(err, check.Equals, nil)
-
-	// Clear ARVADOS_API_* env vars before starting up the server,
-	// to make sure arvados-git-httpd doesn't use them or complain
-	// about them being missing.
-	os.Unsetenv("ARVADOS_API_HOST")
-	os.Unsetenv("ARVADOS_API_HOST_INSECURE")
-	os.Unsetenv("ARVADOS_API_TOKEN")
-}
-
-func (s *IntegrationSuite) TearDownTest(c *check.C) {
-	var err error
-	if s.testServer != nil {
-		err = s.testServer.Close()
-	}
-	c.Check(err, check.Equals, nil)
-	s.testServer = nil
-
-	if s.tmpRepoRoot != "" {
-		err = os.RemoveAll(s.tmpRepoRoot)
-		c.Check(err, check.Equals, nil)
-	}
-	s.tmpRepoRoot = ""
-
-	if s.tmpWorkdir != "" {
-		err = os.RemoveAll(s.tmpWorkdir)
-		c.Check(err, check.Equals, nil)
-	}
-	s.tmpWorkdir = ""
-
-	s.cluster = nil
-}
-
-func (s *IntegrationSuite) RunGit(c *check.C, token, gitCmd, repo string, args ...string) error {
-	cwd, err := os.Getwd()
-	c.Assert(err, check.Equals, nil)
-	defer os.Chdir(cwd)
-	os.Chdir(s.tmpWorkdir)
-
-	gitargs := append([]string{
-		gitCmd, "http://" + s.testServer.Addr + "/" + repo,
-	}, args...)
-	cmd := exec.Command("git", gitargs...)
-	cmd.Env = append(os.Environ(), "ARVADOS_API_TOKEN="+token)
-	w, err := cmd.StdinPipe()
-	c.Assert(err, check.Equals, nil)
-	w.Close()
-	output, err := cmd.CombinedOutput()
-	c.Log("git ", gitargs, " => ", err)
-	c.Log(string(output))
-	if err != nil && len(output) > 0 {
-		// If messages appeared on stderr, they are more
-		// helpful than the err returned by CombinedOutput().
-		//
-		// Easier to match error strings without newlines:
-		err = errors.New(strings.Replace(string(output), "\n", " // ", -1))
-	}
-	return err
-}
diff --git a/services/githttpd/server_test.go b/services/githttpd/server_test.go
deleted file mode 100644
index 02c13a3112..0000000000
--- a/services/githttpd/server_test.go
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-package githttpd
-
-import (
-	"os"
-	"os/exec"
-
-	check "gopkg.in/check.v1"
-)
-
-var _ = check.Suite(&GitSuite{})
-
-const (
-	spectatorToken = "zw2f4gwx8hw8cjre7yp6v1zylhrhn3m5gvjq73rtpwhmknrybu"
-	activeToken    = "3kg6k6lzmp9kj5cpkcoxie963cmvjahbt2fod9zru30k1jqdmi"
-	anonymousToken = "4kg6k6lzmp9kj4cpkcoxie964cmvjahbt4fod9zru44k4jqdmi"
-	expiredToken   = "2ym314ysp27sk7h943q6vtc378srb06se3pq6ghurylyf3pdmx"
-)
-
-type GitSuite struct {
-	IntegrationSuite
-}
-
-func (s *GitSuite) TestPathVariants(c *check.C) {
-	s.makeArvadosRepo(c)
-	for _, repo := range []string{"active/foo.git", "active/foo/.git", "arvados.git", "arvados/.git"} {
-		err := s.RunGit(c, spectatorToken, "fetch", repo, "refs/heads/main")
-		c.Assert(err, check.Equals, nil)
-	}
-}
-
-func (s *GitSuite) TestReadonly(c *check.C) {
-	err := s.RunGit(c, spectatorToken, "fetch", "active/foo.git", "refs/heads/main")
-	c.Assert(err, check.Equals, nil)
-	err = s.RunGit(c, spectatorToken, "push", "active/foo.git", "main:newbranchfail")
-	c.Assert(err, check.ErrorMatches, `.*HTTP (code = )?403.*`)
-	_, err = os.Stat(s.tmpRepoRoot + "/zzzzz-s0uqq-382brsig8rp3666.git/refs/heads/newbranchfail")
-	c.Assert(err, check.FitsTypeOf, &os.PathError{})
-}
-
-func (s *GitSuite) TestReadwrite(c *check.C) {
-	err := s.RunGit(c, activeToken, "fetch", "active/foo.git", "refs/heads/main")
-	c.Assert(err, check.Equals, nil)
-	err = s.RunGit(c, activeToken, "push", "active/foo.git", "main:newbranch")
-	c.Assert(err, check.Equals, nil)
-	_, err = os.Stat(s.tmpRepoRoot + "/zzzzz-s0uqq-382brsig8rp3666.git/refs/heads/newbranch")
-	c.Assert(err, check.Equals, nil)
-}
-
-func (s *GitSuite) TestNonexistent(c *check.C) {
-	err := s.RunGit(c, spectatorToken, "fetch", "thisrepodoesnotexist.git", "refs/heads/main")
-	c.Assert(err, check.ErrorMatches, `.* not found.*`)
-}
-
-func (s *GitSuite) TestMissingGitdirReadableRepository(c *check.C) {
-	err := s.RunGit(c, activeToken, "fetch", "active/foo2.git", "refs/heads/main")
-	c.Assert(err, check.ErrorMatches, `.* not found.*`)
-}
-
-func (s *GitSuite) TestNoPermission(c *check.C) {
-	for _, repo := range []string{"active/foo.git", "active/foo/.git"} {
-		err := s.RunGit(c, anonymousToken, "fetch", repo, "refs/heads/main")
-		c.Assert(err, check.ErrorMatches, `.* not found.*`)
-	}
-}
-
-func (s *GitSuite) TestExpiredToken(c *check.C) {
-	for _, repo := range []string{"active/foo.git", "active/foo/.git"} {
-		err := s.RunGit(c, expiredToken, "fetch", repo, "refs/heads/main")
-		c.Assert(err, check.ErrorMatches, `.* (500 while accessing|requested URL returned error: 500).*`)
-	}
-}
-
-func (s *GitSuite) TestInvalidToken(c *check.C) {
-	for _, repo := range []string{"active/foo.git", "active/foo/.git"} {
-		err := s.RunGit(c, "s3cr3tp at ssw0rd", "fetch", repo, "refs/heads/main")
-		c.Assert(err, check.ErrorMatches, `.* requested URL returned error.*`)
-	}
-}
-
-func (s *GitSuite) TestShortToken(c *check.C) {
-	for _, repo := range []string{"active/foo.git", "active/foo/.git"} {
-		err := s.RunGit(c, "s3cr3t", "fetch", repo, "refs/heads/main")
-		c.Assert(err, check.ErrorMatches, `.* (500 while accessing|requested URL returned error: 500).*`)
-	}
-}
-
-func (s *GitSuite) TestShortTokenBadReq(c *check.C) {
-	for _, repo := range []string{"bogus"} {
-		err := s.RunGit(c, "s3cr3t", "fetch", repo, "refs/heads/main")
-		c.Assert(err, check.ErrorMatches, `.*not found.*`)
-	}
-}
-
-// Make a bare arvados repo at {tmpRepoRoot}/arvados.git
-func (s *GitSuite) makeArvadosRepo(c *check.C) {
-	msg, err := exec.Command("git", "init", "--bare", s.tmpRepoRoot+"/zzzzz-s0uqq-arvadosrepo0123.git").CombinedOutput()
-	c.Log(string(msg))
-	c.Assert(err, check.Equals, nil)
-	msg, err = exec.Command("git", "--git-dir", s.tmpRepoRoot+"/zzzzz-s0uqq-arvadosrepo0123.git", "fetch", "../../.git", "HEAD:main").CombinedOutput()
-	c.Log(string(msg))
-	c.Assert(err, check.Equals, nil)
-}
diff --git a/services/workbench2/cypress/e2e/search.cy.js b/services/workbench2/cypress/e2e/search.cy.js
index 094a3f618c..4e5aa31f4d 100644
--- a/services/workbench2/cypress/e2e/search.cy.js
+++ b/services/workbench2/cypress/e2e/search.cy.js
@@ -215,8 +215,6 @@ describe("Search tests", function () {
                         DispatchCloud: { ExternalURL: "" },
                         DispatchLSF: { ExternalURL: "" },
                         DispatchSLURM: { ExternalURL: "" },
-                        GitHTTP: { ExternalURL: "https://xxxxx.fakecluster.tld:39105/" },
-                        GitSSH: { ExternalURL: "" },
                         Health: { ExternalURL: "https://xxxxx.fakecluster.tld:42915/" },
                         Keepbalance: { ExternalURL: "" },
                         Keepproxy: { ExternalURL: "https://xxxxx.fakecluster.tld:46773/" },
diff --git a/tools/arvbox/lib/arvbox/docker/Dockerfile.base b/tools/arvbox/lib/arvbox/docker/Dockerfile.base
index d8b2408831..927b975f8f 100644
--- a/tools/arvbox/lib/arvbox/docker/Dockerfile.base
+++ b/tools/arvbox/lib/arvbox/docker/Dockerfile.base
@@ -137,8 +137,7 @@ RUN echo arvados_version is git commit $arvados_version
 
 COPY $workdir/fuse.conf /etc/
 
-COPY $workdir/gitolite.rc \
-    $workdir/keep-setup.sh $workdir/common.sh $workdir/createusers.sh \
+COPY $workdir/keep-setup.sh $workdir/common.sh $workdir/createusers.sh \
     $workdir/logger $workdir/runsu.sh $workdir/waitforpostgres.sh \
     $workdir/yml_override.py $workdir/api-setup.sh \
     $workdir/go-setup.sh $workdir/devenv.sh $workdir/cluster-config.sh $workdir/edit_users.py \
diff --git a/tools/arvbox/lib/arvbox/docker/Dockerfile.demo b/tools/arvbox/lib/arvbox/docker/Dockerfile.demo
index 81a5369f5e..dfbeaa448c 100644
--- a/tools/arvbox/lib/arvbox/docker/Dockerfile.demo
+++ b/tools/arvbox/lib/arvbox/docker/Dockerfile.demo
@@ -37,7 +37,6 @@ RUN sudo -u arvbox /var/lib/arvbox/service/keep-web/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/keepproxy/run-service --only-deps
-RUN sudo -u arvbox /var/lib/arvbox/service/arv-git-httpd/run-service --only-deps
 RUN sudo -u arvbox /var/lib/arvbox/service/websockets/run --only-deps
 RUN sudo -u arvbox /usr/local/lib/arvbox/keep-setup.sh --only-deps
 RUN sudo -u arvbox /var/lib/arvbox/service/sdk/run-service
diff --git a/tools/arvbox/lib/arvbox/docker/api-setup.sh b/tools/arvbox/lib/arvbox/docker/api-setup.sh
index 29cea1ecbe..b2d20a455c 100755
--- a/tools/arvbox/lib/arvbox/docker/api-setup.sh
+++ b/tools/arvbox/lib/arvbox/docker/api-setup.sh
@@ -38,8 +38,6 @@ $RAILS_ENV:
   blob_signing_key: $blob_signing_key
   workbench_address: "https://$localip/"
   websocket_address: "wss://$localip:${services[websockets-ssl]}/websocket"
-  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
diff --git a/tools/arvbox/lib/arvbox/docker/cluster-config.sh b/tools/arvbox/lib/arvbox/docker/cluster-config.sh
index 9b55181c91..d07fc3d34d 100755
--- a/tools/arvbox/lib/arvbox/docker/cluster-config.sh
+++ b/tools/arvbox/lib/arvbox/docker/cluster-config.sh
@@ -87,12 +87,6 @@ Clusters:
         ExternalURL: "wss://$localip:${services[websockets-ssl]}/websocket"
         InternalURLs:
           "http://localhost:${services[websockets]}": {}
-      GitSSH:
-        ExternalURL: "ssh://git@$localip:"
-      GitHTTP:
-        InternalURLs:
-          "http://localhost:${services[arv-git-httpd]}/": {}
-        ExternalURL: "https://$localip:${services[arv-git-httpd-ssl]}/"
       WebDAV:
         InternalURLs:
           "http://localhost:${services[keep-web]}/": {}
@@ -132,11 +126,6 @@ Clusters:
       AutoAdminFirstUser: true
       AutoSetupNewUsers: true
       AutoSetupNewUsersWithVmUUID: $vm_uuid
-      AutoSetupNewUsersWithRepository: true
-    Git:
-      GitCommand: /usr/share/gitolite3/gitolite-shell
-      GitoliteHome: $ARVADOS_CONTAINER_PATH/git
-      Repositories: $ARVADOS_CONTAINER_PATH/git/repositories
     Volumes:
       ${uuid_prefix}-nyw5e-000000000000000:
         Driver: Directory
diff --git a/tools/arvbox/lib/arvbox/docker/common.sh b/tools/arvbox/lib/arvbox/docker/common.sh
index 54ec9403ad..270507c5ba 100644
--- a/tools/arvbox/lib/arvbox/docker/common.sh
+++ b/tools/arvbox/lib/arvbox/docker/common.sh
@@ -40,8 +40,6 @@ services=(
   [api]=8004
   [controller]=8003
   [controller-ssl]=8000
-  [arv-git-httpd-ssl]=9000
-  [arv-git-httpd]=9001
   [keep-web]=9003
   [keep-web-ssl]=9002
   [keep-web-dl-ssl]=9004
diff --git a/tools/arvbox/lib/arvbox/docker/gitolite.rc b/tools/arvbox/lib/arvbox/docker/gitolite.rc
deleted file mode 100644
index 07a9ce0d44..0000000000
--- a/tools/arvbox/lib/arvbox/docker/gitolite.rc
+++ /dev/null
@@ -1,217 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-# 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/service/arv-git-httpd/log/main/.gitstub b/tools/arvbox/lib/arvbox/docker/service/arv-git-httpd/log/main/.gitstub
deleted file mode 100644
index e69de29bb2..0000000000
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
deleted file mode 120000
index d6aef4a77d..0000000000
--- a/tools/arvbox/lib/arvbox/docker/service/arv-git-httpd/log/run
+++ /dev/null
@@ -1 +0,0 @@
-/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
deleted file mode 120000
index a388c8b67b..0000000000
--- a/tools/arvbox/lib/arvbox/docker/service/arv-git-httpd/run
+++ /dev/null
@@ -1 +0,0 @@
-/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
deleted file mode 100755
index b598a75d89..0000000000
--- a/tools/arvbox/lib/arvbox/docker/service/arv-git-httpd/run-service
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/bash
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-exec 2>&1
-set -ex -o pipefail
-
-. /usr/local/lib/arvbox/common.sh
-. /usr/local/lib/arvbox/go-setup.sh
-
-(cd /usr/local/bin && ln -sf arvados-server arvados-git-httpd)
-
-if test "$1" = "--only-deps" ; then
-    exit
-fi
-
-flock $ARVADOS_CONTAINER_PATH/cluster_config.yml.lock /usr/local/lib/arvbox/cluster-config.sh
-
-export PATH="$PATH:$ARVADOS_CONTAINER_PATH/git/bin"
-cd ~git
-exec /usr/local/bin/arvados-git-httpd
diff --git a/tools/arvbox/lib/arvbox/docker/service/gitolite/log/main/.gitstub b/tools/arvbox/lib/arvbox/docker/service/gitolite/log/main/.gitstub
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/tools/arvbox/lib/arvbox/docker/service/gitolite/log/run b/tools/arvbox/lib/arvbox/docker/service/gitolite/log/run
deleted file mode 120000
index d6aef4a77d..0000000000
--- a/tools/arvbox/lib/arvbox/docker/service/gitolite/log/run
+++ /dev/null
@@ -1 +0,0 @@
-/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
deleted file mode 120000
index a388c8b67b..0000000000
--- a/tools/arvbox/lib/arvbox/docker/service/gitolite/run
+++ /dev/null
@@ -1 +0,0 @@
-/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
deleted file mode 100755
index 5f2cbc8825..0000000000
--- a/tools/arvbox/lib/arvbox/docker/service/gitolite/run-service
+++ /dev/null
@@ -1,130 +0,0 @@
-#!/bin/bash
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-exec 2>&1
-set -eux -o pipefail
-
-. /usr/local/lib/arvbox/common.sh
-
-if test "$1" != "--only-deps" ; then
-  while [ ! -f $ARVADOS_CONTAINER_PATH/api.ready ]; do
-    sleep 1
-  done
-fi
-
-mkdir -p $ARVADOS_CONTAINER_PATH/git
-
-export ARVADOS_API_HOST=$localip:${services[controller-ssl]}
-export ARVADOS_API_HOST_INSECURE=1
-export ARVADOS_API_TOKEN=$(cat $ARVADOS_CONTAINER_PATH/superuser_token)
-
-export USER=git
-export USERNAME=git
-export LOGNAME=git
-export HOME=$ARVADOS_CONTAINER_PATH/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 $ARVADOS_CONTAINER_PATH/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 at 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 at 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 $ARVADOS_CONTAINER_PATH/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 at localhost
-fi
-
-prefix=$(arv --format=uuid user current | cut -d- -f1)
-
-if ! test -s $ARVADOS_CONTAINER_PATH/arvados-git-uuid ; then
-    repo_uuid=$(arv --format=uuid repository create --repository "{\"owner_uuid\":\"$prefix-tpzed-000000000000000\", \"name\":\"arvados\"}")
-    echo $repo_uuid > $ARVADOS_CONTAINER_PATH/arvados-git-uuid
-fi
-
-repo_uuid=$(cat $ARVADOS_CONTAINER_PATH/arvados-git-uuid)
-
-if ! test -s $ARVADOS_CONTAINER_PATH/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 > $ARVADOS_CONTAINER_PATH/arvados-git-link-uuid
-fi
-
-if ! test -d $ARVADOS_CONTAINER_PATH/git/repositories/$repo_uuid.git ; then
-    git clone --bare /usr/src/arvados $ARVADOS_CONTAINER_PATH/git/repositories/$repo_uuid.git
-else
-    git --git-dir=$ARVADOS_CONTAINER_PATH/git/repositories/$repo_uuid.git fetch -f /usr/src/arvados main:main
-fi
-
-cd /usr/src/arvados/services/api
-
-if test -s $ARVADOS_CONTAINER_PATH/api_rails_env ; then
-  RAILS_ENV=$(cat $ARVADOS_CONTAINER_PATH/api_rails_env)
-else
-  RAILS_ENV=development
-fi
-
-git_user_key=$(cat ~git/.ssh/id_rsa.pub)
-
-cat > config/arvados-clients.yml <<EOF
-$RAILS_ENV:
-  gitolite_url: $ARVADOS_CONTAINER_PATH/git/repositories/gitolite-admin.git
-  gitolite_tmp: $ARVADOS_CONTAINER_PATH/git
-  arvados_api_host: $localip:${services[controller-ssl]}
-  arvados_api_token: "$ARVADOS_API_TOKEN"
-  arvados_api_host_insecure: false
-  gitolite_arvados_git_user_key: "$git_user_key"
-EOF
-
-while true ; do
-    flock $GEMLOCK bundle exec script/arvados-git-sync.rb $RAILS_ENV
-    sleep 120
-done
diff --git a/tools/arvbox/lib/arvbox/docker/service/nginx/run b/tools/arvbox/lib/arvbox/docker/service/nginx/run
index 528c6be827..1ac38035bc 100755
--- a/tools/arvbox/lib/arvbox/docker/service/nginx/run
+++ b/tools/arvbox/lib/arvbox/docker/service/nginx/run
@@ -233,75 +233,51 @@ http {
     }
   }
 
-  upstream arvados-git-httpd {
-    server localhost:${services[arv-git-httpd]};
+  upstream arvados-webshell {
+    server                localhost:${services[webshell]};
   }
   server {
-    listen *:${services[arv-git-httpd-ssl]} ssl default_server;
-    server_name arvados-git-httpd;
+    listen                ${services[webshell-ssl]} ssl;
+    server_name           arvados-webshell;
+
     proxy_connect_timeout 90s;
-    proxy_read_timeout 300s;
+    proxy_read_timeout    300s;
 
-    ssl on;
+    ssl                   on;
     ssl_certificate "${server_cert}";
     ssl_certificate_key "${server_cert_key}";
-    client_max_body_size 50m;
-
-    location  / {
-      proxy_pass http://arvados-git-httpd;
-      proxy_set_header Host \$http_host;
-      proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
-      proxy_set_header X-Forwarded-Proto https;
-      proxy_redirect off;
-    }
-  }
-
 
-upstream arvados-webshell {
-  server                localhost:${services[webshell]};
-}
-server {
-  listen                ${services[webshell-ssl]} ssl;
-  server_name           arvados-webshell;
-
-  proxy_connect_timeout 90s;
-  proxy_read_timeout    300s;
-
-  ssl                   on;
-  ssl_certificate "${server_cert}";
-  ssl_certificate_key "${server_cert_key}";
-
-  location / {
-    if (\$request_method = 'OPTIONS') {
-       add_header 'Access-Control-Allow-Origin' '*';
-       add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
-       add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
-       add_header 'Access-Control-Max-Age' 1728000;
-       add_header 'Content-Type' 'text/plain charset=UTF-8';
-       add_header 'Content-Length' 0;
-       return 204;
-    }
-    if (\$request_method = 'POST') {
-       add_header 'Access-Control-Allow-Origin' '*';
-       add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
-       add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
-    }
-    if (\$request_method = 'GET') {
-       add_header 'Access-Control-Allow-Origin' '*';
-       add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
-       add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
+    location / {
+      if (\$request_method = 'OPTIONS') {
+         add_header 'Access-Control-Allow-Origin' '*';
+         add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
+         add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
+         add_header 'Access-Control-Max-Age' 1728000;
+         add_header 'Content-Type' 'text/plain charset=UTF-8';
+         add_header 'Content-Length' 0;
+         return 204;
+      }
+      if (\$request_method = 'POST') {
+         add_header 'Access-Control-Allow-Origin' '*';
+         add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
+         add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
+      }
+      if (\$request_method = 'GET') {
+         add_header 'Access-Control-Allow-Origin' '*';
+         add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
+         add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
+      }
+
+      proxy_ssl_session_reuse off;
+      proxy_read_timeout  90;
+      proxy_set_header    X-Forwarded-Proto https;
+      proxy_set_header    Host \$http_host;
+      proxy_set_header    X-Real-IP \$remote_addr;
+      proxy_set_header    X-Forwarded-For \$proxy_add_x_forwarded_for;
+      proxy_pass          http://arvados-webshell;
     }
-
-    proxy_ssl_session_reuse off;
-    proxy_read_timeout  90;
-    proxy_set_header    X-Forwarded-Proto https;
-    proxy_set_header    Host \$http_host;
-    proxy_set_header    X-Real-IP \$remote_addr;
-    proxy_set_header    X-Forwarded-For \$proxy_add_x_forwarded_for;
-    proxy_pass          http://arvados-webshell;
   }
 }
-}
 
 EOF
 
diff --git a/tools/arvbox/lib/arvbox/docker/service/vm/run b/tools/arvbox/lib/arvbox/docker/service/vm/run
index 61430d02bd..17b661442e 100755
--- a/tools/arvbox/lib/arvbox/docker/service/vm/run
+++ b/tools/arvbox/lib/arvbox/docker/service/vm/run
@@ -7,9 +7,6 @@ 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
diff --git a/tools/salt-install/config_examples/multi_host/aws/pillars/arvados.sls b/tools/salt-install/config_examples/multi_host/aws/pillars/arvados.sls
index 16e686ab80..ce95a256b0 100644
--- a/tools/salt-install/config_examples/multi_host/aws/pillars/arvados.sls
+++ b/tools/salt-install/config_examples/multi_host/aws/pillars/arvados.sls
@@ -166,7 +166,6 @@ arvados:
       NewUsersAreActive: true
       AutoAdminFirstUser: true
       AutoSetupNewUsers: true
-      AutoSetupNewUsersWithRepository: true
 
     Services:
       Controller:
diff --git a/tools/salt-install/config_examples/single_host/multiple_hostnames/pillars/arvados.sls b/tools/salt-install/config_examples/single_host/multiple_hostnames/pillars/arvados.sls
index 271ab50290..a2bfdb19b0 100644
--- a/tools/salt-install/config_examples/single_host/multiple_hostnames/pillars/arvados.sls
+++ b/tools/salt-install/config_examples/single_host/multiple_hostnames/pillars/arvados.sls
@@ -128,7 +128,6 @@ arvados:
       NewUsersAreActive: true
       AutoAdminFirstUser: true
       AutoSetupNewUsers: true
-      AutoSetupNewUsersWithRepository: true
 
     Services:
       Controller:
diff --git a/tools/salt-install/config_examples/single_host/single_hostname/pillars/arvados.sls b/tools/salt-install/config_examples/single_host/single_hostname/pillars/arvados.sls
index 9e3a293110..03db62ab5d 100644
--- a/tools/salt-install/config_examples/single_host/single_hostname/pillars/arvados.sls
+++ b/tools/salt-install/config_examples/single_host/single_hostname/pillars/arvados.sls
@@ -131,7 +131,6 @@ arvados:
       NewUsersAreActive: true
       AutoAdminFirstUser: true
       AutoSetupNewUsers: true
-      AutoSetupNewUsersWithRepository: true
 
     Services:
       Controller:

commit 49c1562fef805fafe3942897dfe209ecba698c88
Author: Tom Clegg <tom at curii.com>
Date:   Wed Apr 17 09:58:09 2024 -0400

    15397: Remove obsolete APIs.
    
    jobs, job_tasks, pipeline_instances, pipeline_templates, nodes,
    repositories, humans, specimens, traits.
    
    Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom at curii.com>

diff --git a/build/run-tests.sh b/build/run-tests.sh
index b8d2081e6e..2d260c4b31 100755
--- a/build/run-tests.sh
+++ b/build/run-tests.sh
@@ -951,15 +951,6 @@ install_services/api() {
         ) || return 1
     fi
 
-    cd "$WORKSPACE/services/api" \
-        && rm -rf tmp/git \
-        && mkdir -p tmp/git \
-        && cd tmp/git \
-        && tar xf ../../test/test.git.tar \
-        && mkdir -p internal.git \
-        && git --git-dir internal.git init \
-            || return 1
-
     (
         set -ex
         cd "$WORKSPACE/services/api"
diff --git a/cmd/arvados-client/cmd.go b/cmd/arvados-client/cmd.go
index 19d13437c8..62630a6a01 100644
--- a/cmd/arvados-client/cmd.go
+++ b/cmd/arvados-client/cmd.go
@@ -37,19 +37,9 @@ var (
 		"container":                cli.APICall,
 		"container_request":        cli.APICall,
 		"group":                    cli.APICall,
-		"human":                    cli.APICall,
-		"job":                      cli.APICall,
-		"job_task":                 cli.APICall,
-		"keep_disk":                cli.APICall,
 		"keep_service":             cli.APICall,
 		"link":                     cli.APICall,
 		"log":                      cli.APICall,
-		"node":                     cli.APICall,
-		"pipeline_instance":        cli.APICall,
-		"pipeline_template":        cli.APICall,
-		"repository":               cli.APICall,
-		"specimen":                 cli.APICall,
-		"trait":                    cli.APICall,
 		"user_agreement":           cli.APICall,
 		"user":                     cli.APICall,
 		"virtual_machine":          cli.APICall,
diff --git a/doc/api/crunch-scripts.html.textile.liquid b/doc/api/crunch-scripts.html.textile.liquid
deleted file mode 100644
index a0d244d9bc..0000000000
--- a/doc/api/crunch-scripts.html.textile.liquid
+++ /dev/null
@@ -1,54 +0,0 @@
----
-layout: default
-navsection: api
-navmenu: Concepts
-title: Crunch scripts
-
-...
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-{% include 'notebox_begin_warning' %}
-This is a legacy API.  This endpoint is deprecated, disabled by default in new installations, and slated to be removed entirely in a future major release of Arvados.  It is replaced by "container requests.":methods/container_requests.html
-{% include 'notebox_end' %}
-
-h2. Crunch scripts
-
-A crunch script is responsible for completing a single JobTask. In doing so, it will:
-
-* (optionally) read some input from Keep
-* (optionally) store some output in Keep
-* (optionally) create some new JobTasks and add them to the current Job
-* (optionally) update the current JobTask record with the "output" attribute set to a Keep locator or a fragment of a manifest
-* update the current JobTask record with the "success" attribute set to True
-
-A task's context is provided in environment variables.
-
-table(table table-bordered table-condensed).
-|Environment variable|Description|
-|@JOB_UUID@|UUID of the current "Job":methods/jobs.html|
-|@TASK_UUID@|UUID of the current "JobTask":methods/job_tasks.html|
-|@ARVADOS_API_HOST@|Hostname and port number of API server|
-|@ARVADOS_API_TOKEN@|Authentication token to use with API calls made by the current task|
-
-The crunch script typically uses the Python SDK (or another suitable client library / SDK) to connect to the Arvados service and retrieve the rest of the details about the current job and task.
-
-The Python SDK has some shortcuts for common operations.
-
-In general, a crunch script can access information about the current job and task like this:
-
-<pre>
-import arvados
-import os
-
-job = arvados.api().jobs().get(uuid=os.environ['JOB_UUID']).execute()
-$sys.stderr.write("script_parameters['foo'] == %s"
-                  % job['script_parameters']['foo'])
-
-task = arvados.api().job_tasks().get(uuid=os.environ['TASK_UUID']).execute()
-$sys.stderr.write("current task sequence number is %d"
-                  % task['sequence'])
-</pre>
diff --git a/doc/api/methods/humans.html.textile.liquid b/doc/api/methods/humans.html.textile.liquid
deleted file mode 100644
index 1c338217eb..0000000000
--- a/doc/api/methods/humans.html.textile.liquid
+++ /dev/null
@@ -1,85 +0,0 @@
----
-layout: default
-navsection: api
-navmenu: API Methods
-title: "humans"
-
-...
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-{% include 'notebox_begin_warning' %}
-This is a legacy API.  This endpoint is deprecated, disabled by default in new installations, and is slated to be removed entirely in a future major release of Arvados.  The recommended way to store metadata is with "'properties' field on collections and projects.":../properties.html
-{% include 'notebox_end' %}
-
-API endpoint base: @https://{{ site.arvados_api_host }}/arvados/v1/humans@
-
-Object type: @7a9it@
-
-Example UUID: @zzzzz-7a9it-0123456789abcde@
-
-h2. Resource
-
-A metadata record that may be used to represent a human subject.
-
-Each Human has, in addition to the "Common resource fields":{{site.baseurl}}/api/resources.html:
-
-table(table table-bordered table-condensed).
-|_. Attribute|_. Type|_. Description|_. Example|
-|properties|hash|||
-
-h2. Methods
-
-See "Common resource methods":{{site.baseurl}}/api/methods.html for more information about @create@, @delete@, @get@, @list@, and @update at .
-
-Required arguments are displayed in %{background:#ccffcc}green%.
-
-h3. create
-
-Create a new Human.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-|human|object||query||
-
-h3. delete
-
-Delete an existing Human.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the Human in question.|path||
-
-h3. get
-
-Gets a Human's metadata by UUID.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the Human in question.|path||
-
-h3. list
-
-List humans.
-
-See "common resource list method.":{{site.baseurl}}/api/methods.html#index
-
-h3. update
-
-Update attributes of an existing Human.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the Human in question.|path||
-|human|object||query||
diff --git a/doc/api/methods/job_tasks.html.textile.liquid b/doc/api/methods/job_tasks.html.textile.liquid
deleted file mode 100644
index 880fe56219..0000000000
--- a/doc/api/methods/job_tasks.html.textile.liquid
+++ /dev/null
@@ -1,101 +0,0 @@
----
-layout: default
-navsection: api
-navmenu: API Methods
-title: "job_tasks"
-
-...
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-{% include 'notebox_begin_warning' %}
-This is a legacy API.  This endpoint is deprecated, disabled by default in new installations, and slated to be removed entirely in a future major release of Arvados.  It is replaced by "container requests.":container_requests.html
-{% include 'notebox_end' %}
-
-API endpoint base: @https://{{ site.arvados_api_host }}/arvados/v1/job_tasks@
-
-Object type: @ot0gb@
-
-Example UUID: @zzzzz-ot0gb-0123456789abcde@
-
-h2. Resource
-
-Deprecated.
-
-A job task is a individually scheduled unit of work executed as part of an overall job.
-
-Each JobTask has, in addition to the "Common resource fields":{{site.baseurl}}/api/resources.html:
-
-table(table table-bordered table-condensed).
-|_. Attribute|_. Type|_. Description|_. Example|
-|sequence|integer|Execution sequence.
-A step cannot be run until all steps with lower sequence numbers have completed.
-Job steps with the same sequence number can be run in any order.||
-|parameters|hash|||
-|output|text|||
-|progress|float|||
-|success|boolean|Is null if the task has neither completed successfully nor failed permanently.||
-
-The following attributes should not be updated by anyone other than the job manager:
-
-table(table table-bordered table-condensed).
-|_. Attribute|_. Type|_. Description|_. Notes|
-|qsequence|integer|Order of arrival|0-based|
-|job_uuid|string|||
-|created_by_job_task_uuid|string|||
-
-h2. Methods
-
-See "Common resource methods":{{site.baseurl}}/api/methods.html for more information about @create@, @delete@, @get@, @list@, and @update at .
-
-Required arguments are displayed in %{background:#ccffcc}green%.
-
-h3. create
-
-Create a new JobTask.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-|job_task|object||query||
-
-h3. delete
-
-Delete an existing JobTask.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the JobTask in question.|path||
-
-h3. get
-
-Gets a JobTask's metadata by UUID.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the JobTask in question.|path||
-
-h3. list
-
-List job_tasks.
-
-See "common resource list method.":{{site.baseurl}}/api/methods.html#index
-
-h3. update
-
-Update attributes of an existing JobTask.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the JobTask in question.|path||
-|job_task|object||query||
diff --git a/doc/api/methods/jobs.html.textile.liquid b/doc/api/methods/jobs.html.textile.liquid
deleted file mode 100644
index 75d7368c8e..0000000000
--- a/doc/api/methods/jobs.html.textile.liquid
+++ /dev/null
@@ -1,290 +0,0 @@
----
-layout: default
-navsection: api
-navmenu: API Methods
-title: "jobs"
-
-...
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-{% include 'notebox_begin_warning' %}
-This is a legacy API.  This endpoint is deprecated, disabled by default in new installations, and slated to be removed entirely in a future major release of Arvados.  It is replaced by "container requests.":container_requests.html
-{% include 'notebox_end' %}
-
-API endpoint base: @https://{{ site.arvados_api_host }}/arvados/v1/jobs@
-
-Object type: @8i9sb@
-
-Example UUID: @zzzzz-8i9sb-0123456789abcde@
-
-h2. Resource
-
-A job describes a work order to be executed by the Arvados cluster.
-
-Each job has, in addition to the "Common resource fields":{{site.baseurl}}/api/resources.html:
-
-table(table table-bordered table-condensed).
-|_. Attribute|_. Type|_. Description|_. Notes|
-|script|string|The filename of the job script.|This program will be invoked by Crunch for each job task. It is given as a path to an executable file, relative to the @/crunch_scripts@ directory in the Git tree specified by the _repository_ and _script_version_ attributes.|
-|script_parameters|hash|The input parameters for the job.|Conventionally, one of the parameters is called @"input"@. Typically, some parameter values are collection UUIDs. Ultimately, though, the significance of parameters is left entirely up to the script itself.|
-|repository|string|Git repository name or URL.|Source of the repository where the given script_version is to be found. This can be given as the name of a locally hosted repository, or as a publicly accessible URL starting with @git://@, @http://@, or @https://@.
-Examples:
- at yourusername/yourrepo@
- at https://github.com/arvados/arvados.git@|
-|script_version|string|Git commit|During a **create** transaction, this is the Git branch, tag, or hash supplied by the client. Before the job starts, Arvados updates it to the full 40-character SHA-1 hash of the commit used by the job.
-See "Specifying Git versions":#script_version below for more detail about acceptable ways to specify a commit.|
-|cancelled_by_client_uuid|string|API client ID|Is null if job has not been cancelled|
-|cancelled_by_user_uuid|string|Authenticated user ID|Is null if job has not been cancelled|
-|cancelled_at|datetime|When job was cancelled|Is null if job has not been cancelled|
-|started_at|datetime|When job started running|Is null if job has not [yet] started|
-|finished_at|datetime|When job finished running|Is null if job has not [yet] finished|
-|running|boolean|Whether the job is running||
-|success|boolean|Whether the job indicated successful completion|Is null if job has not finished|
-|is_locked_by_uuid|string|UUID of the user who has locked this job|Is null if job is not locked. The system user locks the job when starting the job, in order to prevent job attributes from being altered.|
-|node_uuids|array|List of UUID strings for node objects that have been assigned to this job||
-|log|string|Collection UUID|Is null if the job has not finished. After the job runs, the given collection contains a text file with log messages provided by the @arv-crunch-job@ task scheduler as well as the standard error streams provided by the task processes.|
-|tasks_summary|hash|Summary of task completion states.|Example: @{"done":0,"running":4,"todo":2,"failed":0}@|
-|output|string|Collection UUID|Is null if the job has not finished.|
-|nondeterministic|boolean|The job is expected to produce different results if run more than once.|If true, this job will not be considered as a candidate for automatic re-use when submitting subsequent identical jobs.|
-|submit_id|string|Unique ID provided by client when job was submitted|Optional. This can be used by a client to make the "jobs.create":{{site.baseurl}}/api/methods/jobs.html#create method idempotent.|
-|priority|string|||
-|arvados_sdk_version|string|Git commit hash that specifies the SDK version to use from the Arvados repository|This is set by searching the Arvados repository for a match for the arvados_sdk_version runtime constraint.|
-|docker_image_locator|string|Portable data hash of the collection that contains the Docker image to use|This is set by searching readable collections for a match for the docker_image runtime constraint.|
-|runtime_constraints|hash|Constraints that must be satisfied by the job/task scheduler in order to run the job.|See below.|
-|components|hash|Name and uuid pairs representing the child work units of this job. The uuids can be of different object types.|Example components hash: @{"name1": "zzzzz-8i9sb-xyz...", "name2": "zzzzz-d1hrv-xyz...",}@|
-
-h3(#script_version). Specifying Git versions
-
-The script_version attribute and arvados_sdk_version runtime constraint are typically given as a branch, tag, or commit hash, but there are many more ways to specify a Git commit. The "specifying revisions" section of the "gitrevisions manual page":http://git-scm.com/docs/gitrevisions.html has a definitive list. Arvados accepts Git versions in any format listed there that names a single commit (not a tree, a blob, or a range of commits). However, some kinds of names can be expected to resolve differently in Arvados than they do in your local repository. For example, <code>HEAD@{1}</code> refers to the local reflog, and @origin/main@ typically refers to a remote branch: neither is likely to work as desired if given as a Git version.
-
-h3. Runtime constraints
-
-table(table table-bordered table-condensed).
-|_. Key|_. Type|_. Description|_. Implemented|
-|arvados_sdk_version|string|The Git version of the SDKs to use from the Arvados git repository.  See "Specifying Git versions":#script_version for more detail about acceptable ways to specify a commit.  If you use this, you must also specify a @docker_image@ constraint (see below).  In order to install the Python SDK successfully, Crunch must be able to find and run virtualenv inside the container.|✓|
-|docker_image|string|The Docker image that this Job needs to run.  If specified, Crunch will create a Docker container from this image, and run the Job's script inside that.  The Keep mount and work directories will be available as volumes inside this container.  The image must be uploaded to Arvados using @arv keep docker at .  You may specify the image in any format that Docker accepts, such as @arvados/jobs@, @debian:latest@, or the Docker image id.  Alternatively, you may specify the portable data hash of the image Collection.|✓|
-|min_nodes|integer||✓|
-|max_nodes|integer|||
-|min_cores_per_node|integer|Require that each node assigned to this Job have the specified number of CPU cores|✓|
-|min_ram_mb_per_node|integer|Require that each node assigned to this Job have the specified amount of real memory (in MiB)|✓|
-|min_scratch_mb_per_node|integer|Require that each node assigned to this Job have the specified amount of scratch storage available (in MiB)|✓|
-|max_tasks_per_node|integer|Maximum simultaneous tasks on a single node|✓|
-|keep_cache_mb_per_task|integer|Size of file data buffer for per-task Keep directory ($TASK_KEEPMOUNT), in MiB.  Default is 256 MiB.  Increase this to reduce cache thrashing in situtations such as accessing multiple large (64+ MiB) files at the same time, or accessing different parts of a large file at the same time.|✓|
-|min_ram_per_task|integer|Minimum real memory (KiB) per task||
-
-h2. Methods
-
-See "Common resource methods":{{site.baseurl}}/api/methods.html for more information about @create@, @delete@, @get@, @list@, and @update at .
-
-Required arguments are displayed in %{background:#ccffcc}green%.
-
-h3. cancel
-
-Cancel a job that is queued or running.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string||path||
-
-h3(#create). create
-
-Create a new Job.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|job|object|Job resource|request body||
-|minimum_script_version |string     |Git branch, tag, or commit hash specifying the minimum acceptable script version (earliest ancestor) to consider when deciding whether to re-use a past job.[1]|query|@"c3e86c9"@|
-|exclude_script_versions|array of strings|Git commit branches, tags, or hashes to exclude when deciding whether to re-use a past job.|query|@["8f03c71","8f03c71"]@
-@["badtag1","badtag2"]@|
-|filters|array of arrays|Conditions to find Jobs to reuse.|query||
-|find_or_create         |boolean    |Before creating, look for an existing job that has identical script, script_version, and script_parameters to those in the present job, has nondeterministic=false, and did not fail (it could be queued, running, or completed). If such a job exists, respond with the existing job instead of submitting a new one.|query|@false@|
-
-When a job is submitted to the queue using the **create** method, the @script_version@ attribute is updated to a full 40-character Git commit hash based on the current content of the specified repository. If @script_version@ cannot be resolved, the job submission is rejected.
-
-fn1. See the "note about specifying Git commits":#script_version for more detail.
-
-h4. Specialized filters
-
-Special filter operations are available for specific Job columns.
-
-* @script_version@ @in git@ @REFSPEC@, @arvados_sdk_version@ @in git@ @REFSPEC@<br>Resolve @REFSPEC@ to a list of Git commits, and match jobs with a @script_version@ or @arvados_sdk_version@ in that list.  When creating a job and filtering @script_version@, the search will find commits between @REFSPEC@ and the submitted job's @script_version@; all other searches will find commits between @REFSPEC@ and HEAD.  This list may include parallel branches if there is more than one path between @REFSPEC@ and the end commit in the graph.  Use @not in@ or @not in git@ filters (below) to blacklist specific commits.
-
-* @script_version@ @not in git@ @REFSPEC@, @arvados_sdk_version@ @not in git@ @REFSPEC@<br>Resolve @REFSPEC@ to a list of Git commits, and match jobs with a @script_version@ or @arvados_sdk_version@ not in that list.
-
-* @docker_image_locator@ @in docker@ @SEARCH@<br>@SEARCH@ can be a Docker image hash, a repository name, or a repository name and tag separated by a colon (@:@).  The server will find collections that contain a Docker image that match that search criteria, then match jobs with a @docker_image_locator@ in that list.
-
-* @docker_image_locator@ @not in docker@ @SEARCH@<br>Negate the @in docker@ filter.
-
-h4. Reusing jobs
-
-Because Arvados records the exact version of the script, input parameters, and runtime environment that was used to run the job, if the script is deterministic (meaning that the same code version is guaranteed to produce the same outputs from the same inputs) then it is possible to re-use the results of past jobs, and avoid re-running the computation to save time.  Arvados uses the following algorithm to determine if a past job can be re-used:
-
-notextile. <div class="spaced-out">
-
-# If @find_or_create@ is false or omitted, create a new job and skip the rest of these steps.
-# If @filters@ are specified, find jobs that match those filters. If any filters are given, there must be at least one filter on the @repository@ attribute and one on the @script@ attribute: otherwise an error is returned.
-# If @filters@ are not specified, find jobs with the same @repository@ and @script@, with a @script_version@ between @minimum_script_version@ and @script_version@ inclusively (excluding @excluded_script_versions@), and a @docker_image_locator@ with the latest Collection that matches the submitted job's @docker_image@ constraint.  If the submitted job includes an @arvados_sdk_version@ constraint, jobs must have an @arvados_sdk_version@ between that refspec and HEAD to be found. *This form is deprecated: use filters instead.*
-# If the found jobs include a completed job, and all found completed jobs have consistent output, return one of them.  Which specific job is returned is undefined.
-# If the found jobs only include incomplete jobs, return one of them.  Which specific job is returned is undefined.
-# If no job has been returned so far, create and return a new job.
-
-</div>
-
-h4. Examples
-
-Run the script "crunch_scripts/hash.py" in the repository "you" using the "main" commit.  Arvados should re-use a previous job if the script_version of the previous job is the same as the current "main" commit. This works irrespective of whether the previous job was submitted using the name "main", a different branch name or tag indicating the same commit, a SHA-1 commit hash, etc.
-
-<notextile><pre>
-{
-  "job": {
-    "script": "hash.py",
-    "repository": "<b>you</b>/<b>you</b>",
-    "script_version": "main",
-    "script_parameters": {
-      "input": "c1bad4b39ca5a924e481008009d94e32+210"
-    }
-  },
-  "find_or_create": true
-}
-</pre></notextile>
-
-Run using exactly the version "d00220fb38d4b85ca8fc28a8151702a2b9d1dec5". Arvados should re-use a previous job if the "script_version" of that job is also "d00220fb38d4b85ca8fc28a8151702a2b9d1dec5".
-
-<notextile><pre>
-{
-  "job": {
-    "script": "hash.py",
-    "repository": "<b>you</b>/<b>you</b>",
-    "script_version": "d00220fb38d4b85ca8fc28a8151702a2b9d1dec5",
-    "script_parameters": {
-      "input": "c1bad4b39ca5a924e481008009d94e32+210"
-    }
-  },
-  "find_or_create": true
-}
-</pre></notextile>
-
-Arvados should re-use a previous job if the "script_version" of the previous job is between "earlier_version_tag" and the "main" commit (inclusive), but not the commit indicated by "blacklisted_version_tag". If there are no previous jobs matching these criteria, run the job using the "main" commit.
-
-<notextile><pre>
-{
-  "job": {
-    "script": "hash.py",
-    "repository": "<b>you</b>/<b>you</b>",
-    "script_version": "main",
-    "script_parameters": {
-      "input": "c1bad4b39ca5a924e481008009d94e32+210"
-    }
-  },
-  "minimum_script_version": "earlier_version_tag",
-  "exclude_script_versions": ["blacklisted_version_tag"],
-  "find_or_create": true
-}
-</pre></notextile>
-
-The same behavior, using filters:
-
-<notextile><pre>
-{
-  "job": {
-    "script": "hash.py",
-    "repository": "<b>you</b>/<b>you</b>",
-    "script_version": "main",
-    "script_parameters": {
-      "input": "c1bad4b39ca5a924e481008009d94e32+210"
-    }
-  },
-  "filters": [["script", "=", "hash.py"],
-              ["repository", "=", "<b>you</b>/<b>you</b>"],
-              ["script_version", "in git", "earlier_version_tag"],
-              ["script_version", "not in git", "blacklisted_version_tag"]],
-  "find_or_create": true
-}
-</pre></notextile>
-
-Run the script "crunch_scripts/monte-carlo.py" in the repository "you/you" using the current "main" commit. Because it is marked as "nondeterministic", this job will not be considered as a suitable candidate for future job submissions that use the "find_or_create" feature.
-
-<notextile><pre>
-{
-  "job": {
-    "script": "monte-carlo.py",
-    "repository": "<b>you</b>/<b>you</b>",
-    "script_version": "main",
-    "nondeterministic": true,
-    "script_parameters": {
-      "input": "c1bad4b39ca5a924e481008009d94e32+210"
-    }
-  }
-}
-</pre></notextile>
-
-h3. delete
-
-Delete an existing Job.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the Job in question.|path||
-
-h3. get
-
-Gets a Job's metadata by UUID.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the Job in question.|path||
-
-h3. list
-
-List jobs.
-
-See "common resource list method.":{{site.baseurl}}/api/methods.html#index
-
-See the create method documentation for more information about Job-specific filters.
-
-h3. log_tail_follow
-
-log_tail_follow jobs
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string||path||
-|buffer_size|integer (default 8192)||query||
-
-h3. queue
-
-Get the current job queue.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-|order|string||query||
-|filters|array||query||
-
-This method is equivalent to the "list method":#list, except that the results are restricted to queued jobs (i.e., jobs that have not yet been started or cancelled) and order defaults to queue priority.
-
-h3. update
-
-Update attributes of an existing Job.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the Job in question.|path||
-|job|object||query||
diff --git a/doc/api/methods/keep_disks.html.textile.liquid b/doc/api/methods/keep_disks.html.textile.liquid
deleted file mode 100644
index 9a82a3e7ce..0000000000
--- a/doc/api/methods/keep_disks.html.textile.liquid
+++ /dev/null
@@ -1,111 +0,0 @@
----
-layout: default
-navsection: api
-navmenu: API Methods
-title: "keep_disks"
-
-...
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-{% include 'notebox_begin_warning' %}
-This is a legacy API.  This endpoint is deprecated, disabled by default in new installations, and slated to be removed entirely in a future major release of Arvados.  It is replaced by "keep services.":keep_services.html
-{% include 'notebox_end' %}
-
-API endpoint base: @https://{{ site.arvados_api_host }}/arvados/v1/keep_disks@
-
-Object type: @penuu@
-
-Example UUID: @zzzzz-penuu-0123456789abcde@
-
-h2. Resource
-
-Obsoleted by "keep_services":{{site.baseurl}}/api/methods/keep_services.html
-
-Each KeepDisk has, in addition to the "Common resource fields":{{site.baseurl}}/api/resources.html:
-
-table(table table-bordered table-condensed).
-|_. Attribute|_. Type|_. Description|_. Example|
-|ping_secret|string|||
-|node_uuid|string|||
-|filesystem_uuid|string|||
-|bytes_total|integer|||
-|bytes_free|integer|||
-|is_readable|boolean|||
-|is_writable|boolean|||
-|last_read_at|datetime|||
-|last_write_at|datetime|||
-|last_ping_at|datetime|||
-|keep_service_uuid|string|||
-
-h2. Methods
-
-See "Common resource methods":{{site.baseurl}}/api/methods.html for more information about @create@, @delete@, @get@, @list@, and @update at .
-
-Required arguments are displayed in %{background:#ccffcc}green%.
-
-h3. create
-
-Create a new KeepDisk.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-|keep_disk|object||query||
-
-h3. delete
-
-Delete an existing KeepDisk.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the KeepDisk in question.|path||
-
-h3. get
-
-Gets a KeepDisk's metadata by UUID.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the KeepDisk in question.|path||
-
-h3. list
-
-List keep_disks.
-
-See "common resource list method.":{{site.baseurl}}/api/methods.html#index
-
-h3. ping
-
-ping keep_disks
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|ping_secret|string||query||
-{background:#ccffcc}.|service_port|string||query||
-{background:#ccffcc}.|service_ssl_flag|string||query||
-|filesystem_uuid|string||query||
-|node_uuid|string||query||
-|service_host|string||query||
-|uuid|string||query||
-
-h3. update
-
-Update attributes of an existing KeepDisk.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the KeepDisk in question.|path||
-|keep_disk|object||query||
diff --git a/doc/api/methods/nodes.html.textile.liquid b/doc/api/methods/nodes.html.textile.liquid
deleted file mode 100644
index b29527ceeb..0000000000
--- a/doc/api/methods/nodes.html.textile.liquid
+++ /dev/null
@@ -1,106 +0,0 @@
----
-layout: default
-navsection: api
-navmenu: API Methods
-title: "nodes"
-
-...
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-{% include 'notebox_begin_warning' %}
-This is a legacy API.  This endpoint is deprecated, disabled by default in new installations, and slated to be removed entirely in a future major release of Arvados.  It is replaced by "cloud dispatcher API.":../dispatch.html
-{% include 'notebox_end' %}
-
-API endpoint base: @https://{{ site.arvados_api_host }}/arvados/v1/nodes@
-
-Object type: @7ekkf@
-
-Example UUID: @zzzzz-7ekkf-0123456789abcde@
-
-h2. Resource
-
-Node resources list compute nodes on which Crunch may schedule work.
-
-Each Node has, in addition to the "Common resource fields":{{site.baseurl}}/api/resources.html:
-
-table(table table-bordered table-condensed).
-|_. Attribute|_. Type|_. Description|_. Example|
-|slot_number|integer|||
-|hostname|string|||
-|domain|string|||
-|ip_address|string|||
-|job_uuid|string|The UUID of the job that this node is assigned to work on.  If you do not have permission to read the job, this will be null.||
-|first_ping_at|datetime|||
-|last_ping_at|datetime|||
-|info|hash|Sensitive information about the node (only visible to admin) such as 'ping_secret' and 'ec2_instance_id'. May be used in queries using "subproperty filters":{{site.baseurl}}/api/methods.html#subpropertyfilters||
-|properties|hash|Public information about the node, such as 'total_cpu_cores', 'total_ram_mb', and 'total_scratch_mb'.  May be used in queries using "subproperty filters":{{site.baseurl}}/api/methods.html#subpropertyfilters||
-
-h2. Methods
-
-See "Common resource methods":{{site.baseurl}}/api/methods.html for more information about @create@, @delete@, @get@, @list@, and @update at .
-
-Required arguments are displayed in %{background:#ccffcc}green%.
-
-h3. create
-
-Create a new Node.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|node|object||query||
-
-h3. delete
-
-Delete an existing Node.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the Node in question.|path||
-
-h3. get
-
-Gets a Node's metadata by UUID.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the Node in question.|path||
-
-h3. list
-
-List nodes.
-
-See "common resource list method.":{{site.baseurl}}/api/methods.html#index
-
-h3. ping
-
-Process a ping from a compute node.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|ping_secret|string||query||
-{background:#ccffcc}.|uuid|string||path||
-
-h3. update
-
-Update attributes of an existing Node.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the Node in question.|path||
-|node|object||query||
-
-To remove a node's job assignment, update the node object's @job_uuid@ to null.
diff --git a/doc/api/methods/pipeline_instances.html.textile.liquid b/doc/api/methods/pipeline_instances.html.textile.liquid
deleted file mode 100644
index e19dfba02a..0000000000
--- a/doc/api/methods/pipeline_instances.html.textile.liquid
+++ /dev/null
@@ -1,90 +0,0 @@
----
-layout: default
-navsection: api
-navmenu: API Methods
-title: "pipeline_instances"
-
-...
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-{% include 'notebox_begin_warning' %}
-This is a legacy API.  This endpoint is deprecated, disabled by default in new installations, and slated to be removed entirely in a future major release of Arvados.  It is replaced by "container requests.":container_requests.html
-{% include 'notebox_end' %}
-
-API endpoint base: @https://{{ site.arvados_api_host }}/arvados/v1/pipeline_instances@
-
-Object type: @d1hrv@
-
-Example UUID: @zzzzz-d1hrv-0123456789abcde@
-
-h2. Resource
-
-Deprecated.  A pipeline instance is a collection of jobs managed by @arvados-run-pipeline-instance at .
-
-Each PipelineInstance has, in addition to the "Common resource fields":{{site.baseurl}}/api/resources.html:
-
-table(table table-bordered table-condensed).
-|_. Attribute|_. Type|_. Description|_. Example|
-|pipeline_template_uuid|string|The "pipeline template":pipeline_templates.html that this instance was created from.||
-|name|string|||
-|components|hash|||
-|success|boolean|||
-|active|boolean|||
-|properties|Hash|||
-
-h2. Methods
-
-See "Common resource methods":{{site.baseurl}}/api/methods.html for more information about @create@, @delete@, @get@, @list@, and @update at .
-
-Required arguments are displayed in %{background:#ccffcc}green%.
-
-h3. create
-
-Create a new PipelineInstance.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-|pipeline_instance|object||query||
-
-h3. delete
-
-Delete an existing PipelineInstance.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the PipelineInstance in question.|path||
-
-h3. get
-
-Gets a PipelineInstance's metadata by UUID.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the PipelineInstance in question.|path||
-
-h3. list
-
-List pipeline_instances.
-
-See "common resource list method.":{{site.baseurl}}/api/methods.html#index
-
-h3. update
-
-Update attributes of an existing PipelineInstance.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the PipelineInstance in question.|path||
-|pipeline_instance|object||query||
diff --git a/doc/api/methods/pipeline_templates.html.textile.liquid b/doc/api/methods/pipeline_templates.html.textile.liquid
deleted file mode 100644
index ddbe8ad389..0000000000
--- a/doc/api/methods/pipeline_templates.html.textile.liquid
+++ /dev/null
@@ -1,228 +0,0 @@
----
-layout: default
-navsection: api
-navmenu: API Methods
-title: "pipeline_templates"
-
-...
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-{% include 'notebox_begin_warning' %}
-This is a legacy API.  This endpoint is deprecated, disabled by default in new installations, and slated to be removed entirely in a future major release of Arvados.  It is replaced by "registered workflows.":workflows.html
-{% include 'notebox_end' %}
-
-API endpoint base: @https://{{ site.arvados_api_host }}/arvados/v1/pipeline_templates@
-
-Object type: @p5p6p@
-
-Example UUID: @zzzzz-p5p6p-0123456789abcde@
-
-h2. Resource
-
-Deprecated.  A pipeline template is a collection of jobs that can be instantiated as a pipeline_instance.
-
-Each PipelineTemplate has, in addition to the "Common resource fields":{{site.baseurl}}/api/resources.html:
-
-table(table table-bordered table-condensed).
-|_. Attribute|_. Type|_. Description|_. Example|
-|name|string|||
-|components|hash|||
-
-The pipeline template consists of "name" and "components".
-
-table(table table-bordered table-condensed).
-|_. Attribute    |_. Type |_. Accepted values                           |_. Required|_. Description|
-|name            |string  |any                                          |yes        |The human-readable name of the pipeline template.|
-|components      |object  |JSON object containing job submission objects|yes        |The component jobs that make up the pipeline, with the component name as the key. |
-
-h3. Components
-
-The components field of the pipeline template is a JSON object which describes the individual steps that make up the pipeline.  Each component is an Arvados job submission.  "Parameters for job submissions are described on the job method page.":{{site.baseurl}}/api/methods/jobs.html#create  In addition, a component can have the following parameters:
-
-table(table table-bordered table-condensed).
-|_. Attribute    |_. Type          |_. Accepted values |_. Required|_. Description|
-|output_name     |string or boolean|string or false    |no         |If a string is provided, use this name for the output collection of this component.  If the value is false, do not create a permanent output collection (an temporary intermediate collection will still be created).  If not provided, a default name will be assigned to the output.|
-
-h3. Script parameters
-
-When used in a pipeline, each parameter in the 'script_parameters' attribute of a component job can specify that the input parameter must be supplied by the user, or the input parameter should be linked to the output of another component.  To do this, the value of the parameter should be JSON object containing one of the following attributes:
-
-table(table table-bordered table-condensed).
-|_. Attribute    |_. Type |_. Accepted values                               |_. Description|
-|default         |any     |any                                              |The default value for this parameter.|
-|required        |boolean |true or false                                    |Specifies whether the parameter is required to have a value or not.|
-|dataclass       |string  |One of 'Collection', 'File' [1], 'number', or 'text' |Data type of this parameter.|
-|search_for      |string  |any string                                       |Substring to use as a default search string when choosing inputs.|
-|output_of       |string  |the name of another component in the pipeline    |Specifies that the value of this parameter should be set to the 'output' attribute of the job that corresponds to the specified component.|
-|title           |string  |any string                                       |User friendly title to display when choosing parameter values|
-|description     |string  |any string                                       |Extended text description for describing expected/valid values for the script parameter|
-|link_name       |string  |any string                                       |User friendly name to display for the parameter value instead of the actual parameter value|
-
-The 'output_of' parameter is especially important, as this is how components are actually linked together to form a pipeline.  Component jobs that depend on the output of other components do not run until the parent job completes and has produced output.  If the parent job fails, the entire pipeline fails.
-
-fn1. The 'File' type refers to a specific file within a Keep collection in the form 'collection_hash/filename', for example '887cd41e9c613463eab2f0d885c6dd96+83/bob.txt'.
-
-The 'search_for' parameter is meaningful only when input dataclass of type Collection or File is used. If a value is provided, this will be preloaded into the input data chooser dialog in Workbench. For example, if your input dataclass is a File and you are interested in a certain filename extention, you can preconfigure it in this attribute.
-
-h3. Examples
-
-This is a pipeline named "Filter MD5 hash values" with two components, "do_hash" and "filter".  The "input" script parameter of the "do_hash" component is required to be filled in by the user, and the expected data type is "Collection".  This also specifies that the "input" script parameter of the "filter" component is the output of "do_hash", so "filter" will not run until "do_hash" completes successfully.  When the pipeline runs, past jobs that meet the criteria described above may be substituted for either or both components to avoid redundant computation.
-
-<notextile><pre>
-{
-  "name": "Filter MD5 hash values",
-  "components": {
-    "do_hash": {
-      "script": "hash.py",
-      "repository": "<b>you</b>/<b>you</b>",
-      "script_version": "main",
-      "script_parameters": {
-        "input": {
-          "required": true,
-          "dataclass": "Collection",
-          "search_for": ".fastq.gz",
-          "title":"Please select a fastq file"
-        }
-      },
-    },
-    "filter": {
-      "script": "0-filter.py",
-      "repository": "<b>you</b>/<b>you</b>",
-      "script_version": "main",
-      "script_parameters": {
-        "input": {
-          "output_of": "do_hash"
-        }
-      },
-    }
-  }
-}
-</pre></notextile>
-
-This pipeline consists of three components.  The components "thing1" and "thing2" both depend on "cat_in_the_hat".  Once the "cat_in_the_hat" job is complete, both "thing1" and "thing2" can run in parallel, because they do not depend on each other.
-
-<notextile><pre>
-{
-  "name": "Wreck the house",
-  "components": {
-    "cat_in_the_hat": {
-      "script": "cat.py",
-      "repository": "<b>you</b>/<b>you</b>",
-      "script_version": "main",
-      "script_parameters": { }
-    },
-    "thing1": {
-      "script": "thing1.py",
-      "repository": "<b>you</b>/<b>you</b>",
-      "script_version": "main",
-      "script_parameters": {
-        "input": {
-          "output_of": "cat_in_the_hat"
-        }
-      },
-    },
-    "thing2": {
-      "script": "thing2.py",
-      "repository": "<b>you</b>/<b>you</b>",
-      "script_version": "main",
-      "script_parameters": {
-        "input": {
-          "output_of": "cat_in_the_hat"
-        }
-      },
-    },
-  }
-}
-</pre></notextile>
-
-This pipeline consists of three components.  The component "cleanup" depends on "thing1" and "thing2".  Both "thing1" and "thing2" are started immediately and can run in parallel, because they do not depend on each other, but "cleanup" cannot begin until both "thing1" and "thing2" have completed.
-
-<notextile><pre>
-{
-  "name": "Clean the house",
-  "components": {
-    "thing1": {
-      "script": "thing1.py",
-      "repository": "<b>you</b>/<b>you</b>",
-      "script_version": "main",
-      "script_parameters": { }
-    },
-    "thing2": {
-      "script": "thing2.py",
-      "repository": "<b>you</b>/<b>you</b>",
-      "script_version": "main",
-      "script_parameters": { }
-    },
-    "cleanup": {
-      "script": "cleanup.py",
-      "repository": "<b>you</b>/<b>you</b>",
-      "script_version": "main",
-      "script_parameters": {
-        "mess1": {
-          "output_of": "thing1"
-        },
-        "mess2": {
-          "output_of": "thing2"
-        }
-      }
-    }
-  }
-}
-</pre></notextile>
-
-h2. Methods
-
-See "Common resource methods":{{site.baseurl}}/api/methods.html for more information about @create@, @delete@, @get@, @list@, and @update at .
-
-Required arguments are displayed in %{background:#ccffcc}green%.
-
-h3. create
-
-Create a new PipelineTemplate.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-|pipeline_template|object||query||
-
-h3. delete
-
-Delete an existing PipelineTemplate.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the PipelineTemplate in question.|path||
-
-h3. get
-
-Gets a PipelineTemplate's metadata by UUID.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the PipelineTemplate in question.|path||
-
-h3. list
-
-List pipeline_templates.
-
-See "common resource list method.":{{site.baseurl}}/api/methods.html#index
-
-h3. update
-
-Update attributes of an existing PipelineTemplate.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the PipelineTemplate in question.|path||
-|pipeline_template|object||query||
diff --git a/doc/api/methods/repositories.html.textile.liquid b/doc/api/methods/repositories.html.textile.liquid
deleted file mode 100644
index b2b2cab7d5..0000000000
--- a/doc/api/methods/repositories.html.textile.liquid
+++ /dev/null
@@ -1,98 +0,0 @@
----
-layout: default
-navsection: api
-navmenu: API Methods
-title: "repositories"
-
-...
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-{% include 'notebox_begin_warning' %}
-This is a legacy API.  This endpoint is deprecated, disabled by default in new installations, and slated to be removed entirely in a future major release of Arvados.  It is replaced by "collection versioning.":collections.html
-{% include 'notebox_end' %}
-
-API endpoint base: @https://{{ site.arvados_api_host }}/arvados/v1/repositories@
-
-Object type: @s0uqq@
-
-Example UUID: @zzzzz-s0uqq-0123456789abcde@
-
-h2. Resource
-
-The repositories resource lists git repositories managed by Arvados.
-
-Each Repository has, in addition to the "Common resource fields":{{site.baseurl}}/api/resources.html:
-
-table(table table-bordered table-condensed).
-|_. Attribute|_. Type|_. Description|_. Example|
-|name|string|The name of the repository on disk.  Repository names must begin with a letter and contain only alphanumerics.  Unless the repository is owned by the system user, the name must begin with the owner's username, then be separated from the base repository name with @/@.  You may not create a repository that is owned by a user without a username.|@username/project1@|
-|clone_urls|array|URLs from which the repository can be cloned. Read-only.|@["git at git.zzzzz.arvadosapi.com:foo/bar.git",
- "https://git.zzzzz.arvadosapi.com/foo/bar.git"]@|
-|fetch_url|string|URL suggested as a fetch-url in git config. Deprecated. Read-only.||
-|push_url|string|URL suggested as a push-url in git config. Deprecated. Read-only.||
-
-h2. Methods
-
-See "Common resource methods":{{site.baseurl}}/api/methods.html for more information about @create@, @delete@, @get@, @list@, and @update at .
-
-Required arguments are displayed in %{background:#ccffcc}green%.
-
-h3. create
-
-Create a new Repository.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-|repository|object||query||
-
-h3. delete
-
-Delete an existing Repository.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the Repository in question.|path||
-
-h3. get
-
-Gets a Repository's metadata by UUID.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the Repository in question.|path||
-
-h3. get_all_permissions
-
-get_all_permissions repositories
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-
-h3. list
-
-List repositories.
-
-See "common resource list method.":{{site.baseurl}}/api/methods.html#index
-
-h3. update
-
-Update attributes of an existing Repository.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the Repository in question.|path||
-|repository|object||query||
diff --git a/doc/api/methods/specimens.html.textile.liquid b/doc/api/methods/specimens.html.textile.liquid
deleted file mode 100644
index 3820eeb242..0000000000
--- a/doc/api/methods/specimens.html.textile.liquid
+++ /dev/null
@@ -1,85 +0,0 @@
----
-layout: default
-navsection: api
-navmenu: API Methods
-title: "specimens"
-...
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-{% include 'notebox_begin_warning' %}
-This is a legacy API.  This endpoint is deprecated, disabled by default in new installations, and is slated to be removed entirely in a future major release of Arvados.  The recommended way to store metadata is with "'properties' field on collections and projects.":../properties.html
-{% include 'notebox_end' %}
-
-API endpoint base: @https://{{ site.arvados_api_host }}/arvados/v1/specimens@
-
-Object type: @j58dm@
-
-Example UUID: @zzzzz-j58dm-0123456789abcde@
-
-h2. Resource
-
-A metadata record that may be used to represent a biological specimen.
-
-Each Specimen has, in addition to the "Common resource fields":{{site.baseurl}}/api/resources.html:
-
-table(table table-bordered table-condensed).
-|_. Attribute|_. Type|_. Description|_. Example|
-|material|string|||
-|properties|hash|||
-
-h2. Methods
-
-See "Common resource methods":{{site.baseurl}}/api/methods.html for more information about @create@, @delete@, @get@, @list@, and @update at .
-
-Required arguments are displayed in %{background:#ccffcc}green%.
-
-h3. create
-
-Create a new Specimen.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-|specimen|object||query||
-
-h3. delete
-
-Delete an existing Specimen.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the Specimen in question.|path||
-
-h3. get
-
-Gets a Specimen's metadata by UUID.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the Specimen in question.|path||
-
-h3. list
-
-List specimens.
-
-See "common resource list method.":{{site.baseurl}}/api/methods.html#index
-
-h3. update
-
-Update attributes of an existing Specimen.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the Specimen in question.|path||
-|specimen|object||query||
diff --git a/doc/api/methods/traits.html.textile.liquid b/doc/api/methods/traits.html.textile.liquid
deleted file mode 100644
index 4e356b9523..0000000000
--- a/doc/api/methods/traits.html.textile.liquid
+++ /dev/null
@@ -1,86 +0,0 @@
----
-layout: default
-navsection: api
-navmenu: API Methods
-title: "traits"
-
-...
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-{% include 'notebox_begin_warning' %}
-This is a legacy API.  This endpoint is deprecated, disabled by default in new installations, and is slated to be removed entirely in a future major release of Arvados.  The recommended way to store metadata is with "'properties' field on collections and projects.":../properties.html
-{% include 'notebox_end' %}
-
-API endpoint base: @https://{{ site.arvados_api_host }}/arvados/v1/traits@
-
-Object type: @q1cn2@
-
-Example UUID: @zzzzz-q1cn2-0123456789abcde@
-
-h2. Resource
-
-A metadata record that may be used to represent a genotype or phenotype trait.
-
-Each Trait has, in addition to the "Common resource fields":{{site.baseurl}}/api/resources.html:
-
-table(table table-bordered table-condensed).
-|_. Attribute|_. Type|_. Description|_. Example|
-|name|string|||
-|properties|hash|||
-
-h2. Methods
-
-See "Common resource methods":{{site.baseurl}}/api/methods.html for more information about @create@, @delete@, @get@, @list@, and @update at .
-
-Required arguments are displayed in %{background:#ccffcc}green%.
-
-h3. create
-
-Create a new Trait.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-|trait|object||query||
-
-h3. delete
-
-Delete an existing Trait.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the Trait in question.|path||
-
-h3. get
-
-Gets a Trait's metadata by UUID.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the Trait in question.|path||
-
-h3. list
-
-List traits.
-
-See "common resource list method.":{{site.baseurl}}/api/methods.html#index
-
-h3. update
-
-Update attributes of an existing Trait.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the Trait in question.|path||
-|trait|object||query||
diff --git a/doc/sdk/cli/index.html.textile.liquid b/doc/sdk/cli/index.html.textile.liquid
index ea10c830bc..827f1d0876 100644
--- a/doc/sdk/cli/index.html.textile.liquid
+++ b/doc/sdk/cli/index.html.textile.liquid
@@ -31,12 +31,12 @@ Available flags:
 Use 'arv subcommand|resource --help' to get more information about a particular
 command or resource.
 
-Available subcommands: copy, create, edit, keep, pipeline, run, tag, ws
+Available subcommands: copy, create, edit, keep, run, tag, ws
 
-Available resources: api_client_authorization, api_client, authorized_key,
-collection, user_agreement, group, job_task, link, log, keep_disk,
-pipeline_instance, node, repository, specimen, pipeline_template, user,
-virtual_machine, trait, human, job, keep_service
+Available resources: api_client_authorization, api_client,
+authorized_key, collection, container, container_request,
+user_agreement, group, keep_service, link, log, user, virtual_machine,
+workflow
 
 Additional options:
   -e, --version       Print version and exit
diff --git a/lib/config/config.default.yml b/lib/config/config.default.yml
index a3ae4fd56b..434ffceac0 100644
--- a/lib/config/config.default.yml
+++ b/lib/config/config.default.yml
@@ -1327,47 +1327,6 @@ Clusters:
         SbatchArgumentsList: []
         SbatchEnvironmentVariables:
           SAMPLE: ""
-        Managed:
-          # Path to dns server configuration directory
-          # (e.g. /etc/unbound.d/conf.d). If false, do not write any config
-          # files or touch restart.txt (see below).
-          DNSServerConfDir: ""
-
-          # Template file for the dns server host snippets. See
-          # unbound.template in this directory for an example. If false, do
-          # not write any config files.
-          DNSServerConfTemplate: ""
-
-          # String to write to {dns_server_conf_dir}/restart.txt (with a
-          # trailing newline) after updating local data. If false, do not
-          # open or write the restart.txt file.
-          DNSServerReloadCommand: ""
-
-          # Command to run after each DNS update. Template variables will be
-          # substituted; see the "unbound" example below. If false, do not run
-          # a command.
-          DNSServerUpdateCommand: ""
-
-          ComputeNodeDomain: ""
-          ComputeNodeNameservers:
-            "192.168.1.1": {}
-            SAMPLE: {}
-
-          # Hostname to assign to a compute node when it sends a "ping" and the
-          # hostname in its Node record is nil.
-          # During bootstrapping, the "ping" script is expected to notice the
-          # hostname given in the ping response, and update its unix hostname
-          # accordingly.
-          # If false, leave the hostname alone (this is appropriate if your compute
-          # nodes' hostnames are already assigned by some other mechanism).
-          #
-          # One way or another, the hostnames of your node records should agree
-          # with your DNS records and your /etc/slurm-llnl/slurm.conf files.
-          #
-          # Example for compute0000, compute0001, ....:
-          # assign_node_hostname: compute%<slot_number>04d
-          # (See http://ruby-doc.org/core-2.2.2/Kernel.html#method-i-format for more.)
-          AssignNodeHostname: "compute%<slot_number>d"
 
       LSF:
         # Arguments to bsub when submitting Arvados containers as LSF jobs.
diff --git a/lib/controller/federation/conn.go b/lib/controller/federation/conn.go
index 949cc56dd2..c2cbfec008 100644
--- a/lib/controller/federation/conn.go
+++ b/lib/controller/federation/conn.go
@@ -605,26 +605,6 @@ func (conn *Conn) LogDelete(ctx context.Context, options arvados.DeleteOptions)
 	return conn.chooseBackend(options.UUID).LogDelete(ctx, options)
 }
 
-func (conn *Conn) SpecimenList(ctx context.Context, options arvados.ListOptions) (arvados.SpecimenList, error) {
-	return conn.generated_SpecimenList(ctx, options)
-}
-
-func (conn *Conn) SpecimenCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Specimen, error) {
-	return conn.chooseBackend(options.ClusterID).SpecimenCreate(ctx, options)
-}
-
-func (conn *Conn) SpecimenUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.Specimen, error) {
-	return conn.chooseBackend(options.UUID).SpecimenUpdate(ctx, options)
-}
-
-func (conn *Conn) SpecimenGet(ctx context.Context, options arvados.GetOptions) (arvados.Specimen, error) {
-	return conn.chooseBackend(options.UUID).SpecimenGet(ctx, options)
-}
-
-func (conn *Conn) SpecimenDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.Specimen, error) {
-	return conn.chooseBackend(options.UUID).SpecimenDelete(ctx, options)
-}
-
 func (conn *Conn) SysTrashSweep(ctx context.Context, options struct{}) (struct{}, error) {
 	return conn.local.SysTrashSweep(ctx, options)
 }
diff --git a/lib/controller/federation/generate.go b/lib/controller/federation/generate.go
index 2dc2918f79..079d908f0d 100644
--- a/lib/controller/federation/generate.go
+++ b/lib/controller/federation/generate.go
@@ -53,7 +53,7 @@ func main() {
 		defer out.Close()
 		out.Write(regexp.MustCompile(`(?ms)^.*package .*?import.*?\n\)\n`).Find(buf))
 		io.WriteString(out, "//\n// -- this file is auto-generated -- do not edit -- edit list.go and run \"go generate\" instead --\n//\n\n")
-		for _, t := range []string{"AuthorizedKey", "Container", "ContainerRequest", "Group", "Specimen", "User", "Link", "Log", "APIClientAuthorization"} {
+		for _, t := range []string{"AuthorizedKey", "Container", "ContainerRequest", "Group", "User", "Link", "Log", "APIClientAuthorization"} {
 			_, err := out.Write(bytes.ReplaceAll(orig, []byte("Collection"), []byte(t)))
 			if err != nil {
 				panic(err)
diff --git a/lib/controller/federation/generated.go b/lib/controller/federation/generated.go
index 8c8666fea1..95f2f650fc 100755
--- a/lib/controller/federation/generated.go
+++ b/lib/controller/federation/generated.go
@@ -181,47 +181,6 @@ func (conn *Conn) generated_GroupList(ctx context.Context, options arvados.ListO
 	return merged, err
 }
 
-func (conn *Conn) generated_SpecimenList(ctx context.Context, options arvados.ListOptions) (arvados.SpecimenList, error) {
-	var mtx sync.Mutex
-	var merged arvados.SpecimenList
-	var needSort atomic.Value
-	needSort.Store(false)
-	err := conn.splitListRequest(ctx, options, func(ctx context.Context, _ string, backend arvados.API, options arvados.ListOptions) ([]string, error) {
-		options.ForwardedFor = conn.cluster.ClusterID + "-" + options.ForwardedFor
-		cl, err := backend.SpecimenList(ctx, options)
-		if err != nil {
-			return nil, err
-		}
-		mtx.Lock()
-		defer mtx.Unlock()
-		if len(merged.Items) == 0 {
-			merged = cl
-		} else if len(cl.Items) > 0 {
-			merged.Items = append(merged.Items, cl.Items...)
-			needSort.Store(true)
-		}
-		uuids := make([]string, 0, len(cl.Items))
-		for _, item := range cl.Items {
-			uuids = append(uuids, item.UUID)
-		}
-		return uuids, nil
-	})
-	if needSort.Load().(bool) {
-		// Apply the default/implied order, "modified_at desc"
-		sort.Slice(merged.Items, func(i, j int) bool {
-			mi, mj := merged.Items[i].ModifiedAt, merged.Items[j].ModifiedAt
-			return mj.Before(mi)
-		})
-	}
-	if merged.Items == nil {
-		// Return empty results as [], not null
-		// (https://github.com/golang/go/issues/27589 might be
-		// a better solution in the future)
-		merged.Items = []arvados.Specimen{}
-	}
-	return merged, err
-}
-
 func (conn *Conn) generated_UserList(ctx context.Context, options arvados.ListOptions) (arvados.UserList, error) {
 	var mtx sync.Mutex
 	var merged arvados.UserList
diff --git a/lib/controller/integration_test.go b/lib/controller/integration_test.go
index 45f35a6d2e..70106ed53e 100644
--- a/lib/controller/integration_test.go
+++ b/lib/controller/integration_test.go
@@ -604,8 +604,6 @@ func (s *IntegrationSuite) TestRequestIDHeader(c *check.C) {
 
 	coll, err := conn1.CollectionCreate(userctx1, arvados.CreateOptions{})
 	c.Check(err, check.IsNil)
-	specimen, err := conn1.SpecimenCreate(userctx1, arvados.CreateOptions{})
-	c.Check(err, check.IsNil)
 
 	tests := []struct {
 		path            string
@@ -618,8 +616,6 @@ func (s *IntegrationSuite) TestRequestIDHeader(c *check.C) {
 		{"/arvados/v1/nonexistant", true, true},
 		{"/arvados/v1/collections/" + coll.UUID, false, false},
 		{"/arvados/v1/collections/" + coll.UUID, true, false},
-		{"/arvados/v1/specimens/" + specimen.UUID, false, false},
-		{"/arvados/v1/specimens/" + specimen.UUID, true, false},
 		// new code path (lib/controller/router etc) - single-cluster request
 		{"/arvados/v1/collections/z1111-4zz18-0123456789abcde", false, true},
 		{"/arvados/v1/collections/z1111-4zz18-0123456789abcde", true, true},
@@ -627,8 +623,8 @@ func (s *IntegrationSuite) TestRequestIDHeader(c *check.C) {
 		{"/arvados/v1/collections/z2222-4zz18-0123456789abcde", false, true},
 		{"/arvados/v1/collections/z2222-4zz18-0123456789abcde", true, true},
 		// old code path (proxyRailsAPI) - single-cluster request
-		{"/arvados/v1/specimens/z1111-j58dm-0123456789abcde", false, true},
-		{"/arvados/v1/specimens/z1111-j58dm-0123456789abcde", true, true},
+		{"/arvados/v1/containers/z1111-dz642-0123456789abcde", false, true},
+		{"/arvados/v1/containers/z1111-dz642-0123456789abcde", true, true},
 		// old code path (setupProxyRemoteCluster) - federated request
 		{"/arvados/v1/workflows/z2222-7fd4e-0123456789abcde", false, true},
 		{"/arvados/v1/workflows/z2222-7fd4e-0123456789abcde", true, true},
diff --git a/lib/controller/router/response.go b/lib/controller/router/response.go
index 42b3435593..97b84df735 100644
--- a/lib/controller/router/response.go
+++ b/lib/controller/router/response.go
@@ -143,11 +143,6 @@ var infixMap = map[string]interface{}{
 	"xvhdp": arvados.ContainerRequest{},
 	"dz642": arvados.Container{},
 	"j7d0g": arvados.Group{},
-	"8i9sb": arvados.Job{},
-	"d1hrv": arvados.PipelineInstance{},
-	"p5p6p": arvados.PipelineTemplate{},
-	"j58dm": arvados.Specimen{},
-	"q1cn2": arvados.Trait{},
 	"7fd4e": arvados.Workflow{},
 }
 
diff --git a/lib/controller/router/router.go b/lib/controller/router/router.go
index 054bcffaf7..39c7d871d8 100644
--- a/lib/controller/router/router.go
+++ b/lib/controller/router/router.go
@@ -472,41 +472,6 @@ func (rtr *router) addRoutes() {
 				return rtr.backend.LogDelete(ctx, *opts.(*arvados.DeleteOptions))
 			},
 		},
-		{
-			arvados.EndpointSpecimenCreate,
-			func() interface{} { return &arvados.CreateOptions{} },
-			func(ctx context.Context, opts interface{}) (interface{}, error) {
-				return rtr.backend.SpecimenCreate(ctx, *opts.(*arvados.CreateOptions))
-			},
-		},
-		{
-			arvados.EndpointSpecimenUpdate,
-			func() interface{} { return &arvados.UpdateOptions{} },
-			func(ctx context.Context, opts interface{}) (interface{}, error) {
-				return rtr.backend.SpecimenUpdate(ctx, *opts.(*arvados.UpdateOptions))
-			},
-		},
-		{
-			arvados.EndpointSpecimenGet,
-			func() interface{} { return &arvados.GetOptions{} },
-			func(ctx context.Context, opts interface{}) (interface{}, error) {
-				return rtr.backend.SpecimenGet(ctx, *opts.(*arvados.GetOptions))
-			},
-		},
-		{
-			arvados.EndpointSpecimenList,
-			func() interface{} { return &arvados.ListOptions{Limit: -1} },
-			func(ctx context.Context, opts interface{}) (interface{}, error) {
-				return rtr.backend.SpecimenList(ctx, *opts.(*arvados.ListOptions))
-			},
-		},
-		{
-			arvados.EndpointSpecimenDelete,
-			func() interface{} { return &arvados.DeleteOptions{} },
-			func(ctx context.Context, opts interface{}) (interface{}, error) {
-				return rtr.backend.SpecimenDelete(ctx, *opts.(*arvados.DeleteOptions))
-			},
-		},
 		{
 			arvados.EndpointAPIClientAuthorizationCreate,
 			func() interface{} { return &arvados.CreateOptions{} },
diff --git a/lib/controller/rpc/conn.go b/lib/controller/rpc/conn.go
index c6be679a25..3125ae29be 100644
--- a/lib/controller/rpc/conn.go
+++ b/lib/controller/rpc/conn.go
@@ -682,41 +682,6 @@ func (conn *Conn) LogDelete(ctx context.Context, options arvados.DeleteOptions)
 	return resp, err
 }
 
-func (conn *Conn) SpecimenCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Specimen, error) {
-	ep := arvados.EndpointSpecimenCreate
-	var resp arvados.Specimen
-	err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
-	return resp, err
-}
-
-func (conn *Conn) SpecimenUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.Specimen, error) {
-	ep := arvados.EndpointSpecimenUpdate
-	var resp arvados.Specimen
-	err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
-	return resp, err
-}
-
-func (conn *Conn) SpecimenGet(ctx context.Context, options arvados.GetOptions) (arvados.Specimen, error) {
-	ep := arvados.EndpointSpecimenGet
-	var resp arvados.Specimen
-	err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
-	return resp, err
-}
-
-func (conn *Conn) SpecimenList(ctx context.Context, options arvados.ListOptions) (arvados.SpecimenList, error) {
-	ep := arvados.EndpointSpecimenList
-	var resp arvados.SpecimenList
-	err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
-	return resp, err
-}
-
-func (conn *Conn) SpecimenDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.Specimen, error) {
-	ep := arvados.EndpointSpecimenDelete
-	var resp arvados.Specimen
-	err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
-	return resp, err
-}
-
 func (conn *Conn) SysTrashSweep(ctx context.Context, options struct{}) (struct{}, error) {
 	ep := arvados.EndpointSysTrashSweep
 	var resp struct{}
diff --git a/lib/controller/rpc/conn_test.go b/lib/controller/rpc/conn_test.go
index 0d1200fe12..ed26e04117 100644
--- a/lib/controller/rpc/conn_test.go
+++ b/lib/controller/rpc/conn_test.go
@@ -100,23 +100,24 @@ func (s *RPCSuite) TestCollectionCreate(c *check.C) {
 	c.Check(coll.UUID, check.HasLen, 27)
 }
 
-func (s *RPCSuite) TestSpecimenCRUD(c *check.C) {
+func (s *RPCSuite) TestGroupCRUD(c *check.C) {
 	s.setupConn(c, os.Getenv("ARVADOS_TEST_API_HOST"))
-	sp, err := s.conn.SpecimenCreate(s.ctx, arvados.CreateOptions{Attrs: map[string]interface{}{
-		"owner_uuid": arvadostest.ActiveUserUUID,
-		"properties": map[string]string{"foo": "bar"},
+	sp, err := s.conn.GroupCreate(s.ctx, arvados.CreateOptions{Attrs: map[string]interface{}{
+		"group_class": "project",
+		"owner_uuid":  arvadostest.ActiveUserUUID,
+		"properties":  map[string]string{"foo": "bar"},
 	}})
 	c.Check(err, check.IsNil)
 	c.Check(sp.UUID, check.HasLen, 27)
 	c.Check(sp.Properties, check.HasLen, 1)
 	c.Check(sp.Properties["foo"], check.Equals, "bar")
 
-	spGet, err := s.conn.SpecimenGet(s.ctx, arvados.GetOptions{UUID: sp.UUID})
+	spGet, err := s.conn.GroupGet(s.ctx, arvados.GetOptions{UUID: sp.UUID})
 	c.Check(err, check.IsNil)
 	c.Check(spGet.UUID, check.Equals, sp.UUID)
 	c.Check(spGet.Properties["foo"], check.Equals, "bar")
 
-	spList, err := s.conn.SpecimenList(s.ctx, arvados.ListOptions{Limit: -1, Filters: []arvados.Filter{{"uuid", "=", sp.UUID}}})
+	spList, err := s.conn.GroupList(s.ctx, arvados.ListOptions{Limit: -1, Filters: []arvados.Filter{{"uuid", "=", sp.UUID}}})
 	c.Check(err, check.IsNil)
 	c.Check(spList.ItemsAvailable, check.Equals, 1)
 	c.Assert(spList.Items, check.HasLen, 1)
@@ -124,12 +125,12 @@ func (s *RPCSuite) TestSpecimenCRUD(c *check.C) {
 	c.Check(spList.Items[0].Properties["foo"], check.Equals, "bar")
 
 	anonCtx := context.WithValue(context.Background(), contextKeyTestTokens, []string{arvadostest.AnonymousToken})
-	spList, err = s.conn.SpecimenList(anonCtx, arvados.ListOptions{Limit: -1, Filters: []arvados.Filter{{"uuid", "=", sp.UUID}}})
+	spList, err = s.conn.GroupList(anonCtx, arvados.ListOptions{Limit: -1, Filters: []arvados.Filter{{"uuid", "=", sp.UUID}}})
 	c.Check(err, check.IsNil)
 	c.Check(spList.ItemsAvailable, check.Equals, 0)
 	c.Check(spList.Items, check.HasLen, 0)
 
-	spDel, err := s.conn.SpecimenDelete(s.ctx, arvados.DeleteOptions{UUID: sp.UUID})
+	spDel, err := s.conn.GroupDelete(s.ctx, arvados.DeleteOptions{UUID: sp.UUID})
 	c.Check(err, check.IsNil)
 	c.Check(spDel.UUID, check.Equals, sp.UUID)
 }
diff --git a/lib/crunchrun/crunchrun.go b/lib/crunchrun/crunchrun.go
index 556a3bfe13..3233ee7e57 100644
--- a/lib/crunchrun/crunchrun.go
+++ b/lib/crunchrun/crunchrun.go
@@ -852,51 +852,36 @@ func (runner *ContainerRunner) LogHostInfo() (err error) {
 
 // LogContainerRecord gets and saves the raw JSON container record from the API server
 func (runner *ContainerRunner) LogContainerRecord() error {
-	logged, err := runner.logAPIResponse("container", "containers", map[string]interface{}{"filters": [][]string{{"uuid", "=", runner.Container.UUID}}}, nil)
+	logged, err := runner.logAPIResponse("container", "containers", map[string]interface{}{"filters": [][]string{{"uuid", "=", runner.Container.UUID}}})
 	if !logged && err == nil {
 		err = fmt.Errorf("error: no container record found for %s", runner.Container.UUID)
 	}
 	return err
 }
 
-// LogNodeRecord logs the current host's InstanceType config entry (or
-// the arvados#node record, if running via crunch-dispatch-slurm).
+// LogNodeRecord logs the current host's InstanceType config entry, if
+// running via arvados-dispatch-cloud.
 func (runner *ContainerRunner) LogNodeRecord() error {
-	if it := os.Getenv("InstanceType"); it != "" {
-		// Dispatched via arvados-dispatch-cloud. Save
-		// InstanceType config fragment received from
-		// dispatcher on stdin.
-		w, err := runner.LogCollection.OpenFile("node.json", os.O_CREATE|os.O_WRONLY, 0666)
-		if err != nil {
-			return err
-		}
-		defer w.Close()
-		_, err = io.WriteString(w, it)
-		if err != nil {
-			return err
-		}
-		return w.Close()
+	it := os.Getenv("InstanceType")
+	if it == "" {
+		// Not dispatched by arvados-dispatch-cloud.
+		return nil
+	}
+	// Save InstanceType config fragment received from dispatcher
+	// on stdin.
+	w, err := runner.LogCollection.OpenFile("node.json", os.O_CREATE|os.O_WRONLY, 0666)
+	if err != nil {
+		return err
 	}
-	// Dispatched via crunch-dispatch-slurm. Look up
-	// apiserver's node record corresponding to
-	// $SLURMD_NODENAME.
-	hostname := os.Getenv("SLURMD_NODENAME")
-	if hostname == "" {
-		hostname, _ = os.Hostname()
+	defer w.Close()
+	_, err = io.WriteString(w, it)
+	if err != nil {
+		return err
 	}
-	_, err := runner.logAPIResponse("node", "nodes", map[string]interface{}{"filters": [][]string{{"hostname", "=", hostname}}}, func(resp interface{}) {
-		// The "info" field has admin-only info when
-		// obtained with a privileged token, and
-		// should not be logged.
-		node, ok := resp.(map[string]interface{})
-		if ok {
-			delete(node, "info")
-		}
-	})
-	return err
+	return w.Close()
 }
 
-func (runner *ContainerRunner) logAPIResponse(label, path string, params map[string]interface{}, munge func(interface{})) (logged bool, err error) {
+func (runner *ContainerRunner) logAPIResponse(label, path string, params map[string]interface{}) (logged bool, err error) {
 	writer, err := runner.LogCollection.OpenFile(label+".json", os.O_CREATE|os.O_WRONLY, 0666)
 	if err != nil {
 		return false, err
@@ -926,9 +911,6 @@ func (runner *ContainerRunner) logAPIResponse(label, path string, params map[str
 	} else if len(items) < 1 {
 		return false, nil
 	}
-	if munge != nil {
-		munge(items[0])
-	}
 	// Re-encode it using indentation to improve readability
 	enc := json.NewEncoder(w)
 	enc.SetIndent("", "    ")
diff --git a/lib/crunchrun/crunchrun_test.go b/lib/crunchrun/crunchrun_test.go
index 276dd36661..1188fe296a 100644
--- a/lib/crunchrun/crunchrun_test.go
+++ b/lib/crunchrun/crunchrun_test.go
@@ -12,6 +12,7 @@ import (
 	"errors"
 	"fmt"
 	"io"
+	"io/fs"
 	"io/ioutil"
 	"log"
 	"math/rand"
@@ -113,6 +114,7 @@ func (s *TestSuite) SetUpTest(c *C) {
 	err = ioutil.WriteFile(s.keepmount+"/by_id/"+fakeInputCollectionPDH+"/input.json", []byte(`{"input":true}`), 0644)
 	c.Assert(err, IsNil)
 	s.runner.ArvMountPoint = s.keepmount
+	os.Setenv("InstanceType", `{"ProviderType":"a1.2xlarge","Price":1.2}`)
 }
 
 type ArvTestClient struct {
@@ -129,8 +131,8 @@ type ArvTestClient struct {
 
 type KeepTestClient struct {
 	Called         bool
-	Content        []byte
 	StorageClasses []string
+	blocks         sync.Map
 }
 
 type stubExecutor struct {
@@ -358,18 +360,30 @@ func (client *KeepTestClient) LocalLocator(locator string) (string, error) {
 }
 
 func (client *KeepTestClient) BlockWrite(_ context.Context, opts arvados.BlockWriteOptions) (arvados.BlockWriteResponse, error) {
-	client.Content = opts.Data
+	locator := fmt.Sprintf("%x+%d", md5.Sum(opts.Data), len(opts.Data))
+	client.blocks.Store(locator, append([]byte(nil), opts.Data...))
 	return arvados.BlockWriteResponse{
-		Locator: fmt.Sprintf("%x+%d", md5.Sum(opts.Data), len(opts.Data)),
+		Locator: locator,
 	}, nil
 }
 
-func (client *KeepTestClient) ReadAt(string, []byte, int) (int, error) {
-	return 0, errors.New("not implemented")
+func (client *KeepTestClient) ReadAt(locator string, dst []byte, offset int) (int, error) {
+	loaded, ok := client.blocks.Load(locator)
+	if !ok {
+		return 0, os.ErrNotExist
+	}
+	data := loaded.([]byte)
+	if offset >= len(data) {
+		return 0, io.EOF
+	}
+	return copy(dst, data[offset:]), nil
 }
 
 func (client *KeepTestClient) Close() {
-	client.Content = nil
+	client.blocks.Range(func(locator, value interface{}) bool {
+		client.blocks.Delete(locator)
+		return true
+	})
 }
 
 func (client *KeepTestClient) SetStorageClasses(sc []string) {
@@ -591,7 +605,7 @@ func (ErrorReader) Seek(int64, int) (int64, error) {
 	return 0, errors.New("ErrorReader")
 }
 
-func (KeepReadErrorTestClient) ManifestFileReader(m manifest.Manifest, filename string) (arvados.File, error) {
+func (*KeepReadErrorTestClient) ManifestFileReader(m manifest.Manifest, filename string) (arvados.File, error) {
 	return ErrorReader{}, nil
 }
 
@@ -1003,9 +1017,8 @@ func (s *TestSuite) TestCrunchstat(c *C) {
 }
 
 func (s *TestSuite) TestNodeInfoLog(c *C) {
-	os.Setenv("SLURMD_NODENAME", "compute2")
 	s.fullRunHelper(c, `{
-		"command": ["sleep", "1"],
+		"command": ["true"],
 		"container_image": "`+arvadostest.DockerImage112PDH+`",
 		"cwd": ".",
 		"environment": {},
@@ -1015,18 +1028,17 @@ func (s *TestSuite) TestNodeInfoLog(c *C) {
 		"runtime_constraints": {},
 		"state": "Locked"
 	}`, nil, func() int {
-		time.Sleep(time.Second)
 		return 0
 	})
 
 	c.Check(s.api.CalledWith("container.exit_code", 0), NotNil)
 	c.Check(s.api.CalledWith("container.state", "Complete"), NotNil)
 
-	c.Assert(s.api.Logs["node"], NotNil)
-	json := s.api.Logs["node"].String()
-	c.Check(json, Matches, `(?ms).*"uuid": *"zzzzz-7ekkf-2z3mc76g2q73aio".*`)
-	c.Check(json, Matches, `(?ms).*"total_cpu_cores": *16.*`)
-	c.Check(json, Not(Matches), `(?ms).*"info":.*`)
+	buf, err := fs.ReadFile(arvados.FS(s.runner.LogCollection), "/node.json")
+	c.Assert(err, IsNil)
+	json := string(buf)
+	c.Check(json, Matches, `(?ms).*"ProviderType": *"a1\.2xlarge".*`)
+	c.Check(json, Matches, `(?ms).*"Price": *1\.2.*`)
 
 	c.Assert(s.api.Logs["node-info"], NotNil)
 	json = s.api.Logs["node-info"].String()
@@ -2386,7 +2398,6 @@ func (s *TestSuite) TestCalculateCost(c *C) {
 	// hasn't found any data), cost is calculated based on
 	// InstanceType env var
 	os.Setenv("InstanceType", `{"Price":1.2}`)
-	defer os.Unsetenv("InstanceType")
 	cost = cr.calculateCost(now)
 	c.Check(cost, Equals, 1.2)
 
@@ -2432,7 +2443,6 @@ func (s *TestSuite) TestSIGUSR2CostUpdate(c *C) {
 	c.Assert(err, IsNil)
 
 	os.Setenv("InstanceType", `{"Price":2.2}`)
-	defer os.Unsetenv("InstanceType")
 	defer func(s string) { lockdir = s }(lockdir)
 	lockdir = c.MkDir()
 
diff --git a/lib/crunchrun/logging_test.go b/lib/crunchrun/logging_test.go
index 42f165fd75..ee3320c7c3 100644
--- a/lib/crunchrun/logging_test.go
+++ b/lib/crunchrun/logging_test.go
@@ -67,7 +67,7 @@ func (s *LoggingTestSuite) TestWriteLogs(c *C) {
 
 	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)
+	s.checkWroteBlock(c, kc, "74561df9ae65ee9f35d5661d42454264+83", logtext)
 }
 
 func (s *LoggingTestSuite) TestWriteLogsLarge(c *C) {
@@ -224,7 +224,14 @@ func (s *LoggingTestSuite) testWriteLogsWithRateLimit(c *C, throttleParam string
 	c.Check(api.Content[0]["log"].(arvadosclient.Dict)["event_type"], Equals, "crunch-run")
 	stderrLog := api.Content[0]["log"].(arvadosclient.Dict)["properties"].(map[string]string)["text"]
 	c.Check(true, Equals, strings.Contains(stderrLog, expected))
-	c.Check(string(kc.Content), Equals, logtext)
+	s.checkWroteBlock(c, kc, "74561df9ae65ee9f35d5661d42454264+83", logtext)
+}
+
+func (s *LoggingTestSuite) checkWroteBlock(c *C, kc *KeepTestClient, locator, expect string) {
+	buf := make([]byte, len([]byte(expect))+1)
+	n, err := kc.ReadAt(locator, buf, 0)
+	c.Check(err, IsNil)
+	c.Check(string(buf[:n]), Equals, expect)
 }
 
 type filterSuite struct{}
diff --git a/sdk/go/arvados/api.go b/sdk/go/arvados/api.go
index c3d0ea8aef..dd1a6c4c32 100644
--- a/sdk/go/arvados/api.go
+++ b/sdk/go/arvados/api.go
@@ -42,11 +42,6 @@ var (
 	EndpointCollectionDelete                = APIEndpoint{"DELETE", "arvados/v1/collections/{uuid}", ""}
 	EndpointCollectionTrash                 = APIEndpoint{"POST", "arvados/v1/collections/{uuid}/trash", ""}
 	EndpointCollectionUntrash               = APIEndpoint{"POST", "arvados/v1/collections/{uuid}/untrash", ""}
-	EndpointSpecimenCreate                  = APIEndpoint{"POST", "arvados/v1/specimens", "specimen"}
-	EndpointSpecimenUpdate                  = APIEndpoint{"PATCH", "arvados/v1/specimens/{uuid}", "specimen"}
-	EndpointSpecimenGet                     = APIEndpoint{"GET", "arvados/v1/specimens/{uuid}", ""}
-	EndpointSpecimenList                    = APIEndpoint{"GET", "arvados/v1/specimens", ""}
-	EndpointSpecimenDelete                  = APIEndpoint{"DELETE", "arvados/v1/specimens/{uuid}", ""}
 	EndpointContainerCreate                 = APIEndpoint{"POST", "arvados/v1/containers", "container"}
 	EndpointContainerUpdate                 = APIEndpoint{"PATCH", "arvados/v1/containers/{uuid}", "container"}
 	EndpointContainerPriorityUpdate         = APIEndpoint{"POST", "arvados/v1/containers/{uuid}/update_priority", "container"}
@@ -332,11 +327,6 @@ type API interface {
 	LogGet(ctx context.Context, options GetOptions) (Log, error)
 	LogList(ctx context.Context, options ListOptions) (LogList, error)
 	LogDelete(ctx context.Context, options DeleteOptions) (Log, error)
-	SpecimenCreate(ctx context.Context, options CreateOptions) (Specimen, error)
-	SpecimenUpdate(ctx context.Context, options UpdateOptions) (Specimen, error)
-	SpecimenGet(ctx context.Context, options GetOptions) (Specimen, error)
-	SpecimenList(ctx context.Context, options ListOptions) (SpecimenList, error)
-	SpecimenDelete(ctx context.Context, options DeleteOptions) (Specimen, error)
 	SysTrashSweep(ctx context.Context, options struct{}) (struct{}, error)
 	UserCreate(ctx context.Context, options CreateOptions) (User, error)
 	UserUpdate(ctx context.Context, options UpdateOptions) (User, error)
diff --git a/sdk/go/arvados/config.go b/sdk/go/arvados/config.go
index 698ee20d8c..483301507b 100644
--- a/sdk/go/arvados/config.go
+++ b/sdk/go/arvados/config.go
@@ -534,15 +534,6 @@ type ContainersConfig struct {
 		PrioritySpread             int64
 		SbatchArgumentsList        []string
 		SbatchEnvironmentVariables map[string]string
-		Managed                    struct {
-			DNSServerConfDir       string
-			DNSServerConfTemplate  string
-			DNSServerReloadCommand string
-			DNSServerUpdateCommand string
-			ComputeNodeDomain      string
-			ComputeNodeNameservers StringSet
-			AssignNodeHostname     string
-		}
 	}
 	LSF struct {
 		BsubSudoUser       string
diff --git a/sdk/go/arvados/specimen.go b/sdk/go/arvados/specimen.go
deleted file mode 100644
index b561fb20ae..0000000000
--- a/sdk/go/arvados/specimen.go
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: Apache-2.0
-
-package arvados
-
-import "time"
-
-type Specimen struct {
-	UUID                 string                 `json:"uuid"`
-	OwnerUUID            string                 `json:"owner_uuid"`
-	CreatedAt            time.Time              `json:"created_at"`
-	ModifiedAt           time.Time              `json:"modified_at"`
-	ModifiedByClientUUID string                 `json:"modified_by_client_uuid"`
-	ModifiedByUserUUID   string                 `json:"modified_by_user_uuid"`
-	Properties           map[string]interface{} `json:"properties"`
-}
-
-type SpecimenList struct {
-	Items          []Specimen `json:"items"`
-	ItemsAvailable int        `json:"items_available"`
-	Offset         int        `json:"offset"`
-	Limit          int        `json:"limit"`
-}
diff --git a/sdk/go/arvadostest/api.go b/sdk/go/arvadostest/api.go
index e1827b5d1f..658874c6d7 100644
--- a/sdk/go/arvadostest/api.go
+++ b/sdk/go/arvadostest/api.go
@@ -264,26 +264,6 @@ func (as *APIStub) LogDelete(ctx context.Context, options arvados.DeleteOptions)
 	as.appendCall(ctx, as.LogDelete, options)
 	return arvados.Log{}, as.Error
 }
-func (as *APIStub) SpecimenCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Specimen, error) {
-	as.appendCall(ctx, as.SpecimenCreate, options)
-	return arvados.Specimen{}, as.Error
-}
-func (as *APIStub) SpecimenUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.Specimen, error) {
-	as.appendCall(ctx, as.SpecimenUpdate, options)
-	return arvados.Specimen{}, as.Error
-}
-func (as *APIStub) SpecimenGet(ctx context.Context, options arvados.GetOptions) (arvados.Specimen, error) {
-	as.appendCall(ctx, as.SpecimenGet, options)
-	return arvados.Specimen{}, as.Error
-}
-func (as *APIStub) SpecimenList(ctx context.Context, options arvados.ListOptions) (arvados.SpecimenList, error) {
-	as.appendCall(ctx, as.SpecimenList, options)
-	return arvados.SpecimenList{}, as.Error
-}
-func (as *APIStub) SpecimenDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.Specimen, error) {
-	as.appendCall(ctx, as.SpecimenDelete, options)
-	return arvados.Specimen{}, as.Error
-}
 func (as *APIStub) SysTrashSweep(ctx context.Context, options struct{}) (struct{}, error) {
 	as.appendCall(ctx, as.SysTrashSweep, options)
 	return struct{}{}, as.Error
diff --git a/sdk/python/arvados-v1-discovery.json b/sdk/python/arvados-v1-discovery.json
index 232c88d067..11a163b08a 100644
--- a/sdk/python/arvados-v1-discovery.json
+++ b/sdk/python/arvados-v1-discovery.json
@@ -2952,17 +2952,17 @@
         }
       }
     },
-    "humans": {
+    "keep_services": {
       "methods": {
         "get": {
-          "id": "arvados.humans.get",
-          "path": "humans/{uuid}",
+          "id": "arvados.keep_services.get",
+          "path": "keep_services/{uuid}",
           "httpMethod": "GET",
-          "description": "Gets a Human's metadata by UUID.",
+          "description": "Gets a KeepService's metadata by UUID.",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the Human in question.",
+              "description": "The UUID of the KeepService in question.",
               "required": true,
               "location": "path"
             }
@@ -2971,7 +2971,7 @@
             "uuid"
           ],
           "response": {
-            "$ref": "Human"
+            "$ref": "KeepService"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
@@ -2979,10 +2979,10 @@
           ]
         },
         "index": {
-          "id": "arvados.humans.list",
-          "path": "humans",
+          "id": "arvados.keep_services.list",
+          "path": "keep_services",
           "httpMethod": "GET",
-          "description": "List Humans.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Humans. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#humanList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
+          "description": "List KeepServices.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching KeepServices. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#keepServiceList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
           "parameters": {
             "filters": {
               "type": "array",
@@ -3050,7 +3050,7 @@
             }
           },
           "response": {
-            "$ref": "HumanList"
+            "$ref": "KeepServiceList"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
@@ -3058,10 +3058,10 @@
           ]
         },
         "create": {
-          "id": "arvados.humans.create",
-          "path": "humans",
+          "id": "arvados.keep_services.create",
+          "path": "keep_services",
           "httpMethod": "POST",
-          "description": "Create a new Human.",
+          "description": "Create a new KeepService.",
           "parameters": {
             "select": {
               "type": "array",
@@ -3086,27 +3086,27 @@
           "request": {
             "required": true,
             "properties": {
-              "human": {
-                "$ref": "Human"
+              "keep_service": {
+                "$ref": "KeepService"
               }
             }
           },
           "response": {
-            "$ref": "Human"
+            "$ref": "KeepService"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "update": {
-          "id": "arvados.humans.update",
-          "path": "humans/{uuid}",
+          "id": "arvados.keep_services.update",
+          "path": "keep_services/{uuid}",
           "httpMethod": "PUT",
-          "description": "Update attributes of an existing Human.",
+          "description": "Update attributes of an existing KeepService.",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the Human in question.",
+              "description": "The UUID of the KeepService in question.",
               "required": true,
               "location": "path"
             },
@@ -3120,43 +3120,56 @@
           "request": {
             "required": true,
             "properties": {
-              "human": {
-                "$ref": "Human"
+              "keep_service": {
+                "$ref": "KeepService"
               }
             }
           },
           "response": {
-            "$ref": "Human"
+            "$ref": "KeepService"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "delete": {
-          "id": "arvados.humans.delete",
-          "path": "humans/{uuid}",
+          "id": "arvados.keep_services.delete",
+          "path": "keep_services/{uuid}",
           "httpMethod": "DELETE",
-          "description": "Delete an existing Human.",
+          "description": "Delete an existing KeepService.",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the Human in question.",
+              "description": "The UUID of the KeepService in question.",
               "required": true,
               "location": "path"
             }
           },
           "response": {
-            "$ref": "Human"
+            "$ref": "KeepService"
+          },
+          "scopes": [
+            "https://api.arvados.org/auth/arvados"
+          ]
+        },
+        "accessible": {
+          "id": "arvados.keep_services.accessible",
+          "path": "keep_services/accessible",
+          "httpMethod": "GET",
+          "description": "accessible keep_services",
+          "parameters": {},
+          "response": {
+            "$ref": "KeepService"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "list": {
-          "id": "arvados.humans.list",
-          "path": "humans",
+          "id": "arvados.keep_services.list",
+          "path": "keep_services",
           "httpMethod": "GET",
-          "description": "List Humans.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Humans. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#humanList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
+          "description": "List KeepServices.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching KeepServices. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#keepServiceList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
           "parameters": {
             "filters": {
               "type": "array",
@@ -3224,7 +3237,7 @@
             }
           },
           "response": {
-            "$ref": "HumanList"
+            "$ref": "KeepServiceList"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
@@ -3232,10 +3245,10 @@
           ]
         },
         "show": {
-          "id": "arvados.humans.show",
-          "path": "humans/{uuid}",
+          "id": "arvados.keep_services.show",
+          "path": "keep_services/{uuid}",
           "httpMethod": "GET",
-          "description": "show humans",
+          "description": "show keep_services",
           "parameters": {
             "uuid": {
               "type": "string",
@@ -3251,17 +3264,17 @@
             }
           },
           "response": {
-            "$ref": "Human"
+            "$ref": "KeepService"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "destroy": {
-          "id": "arvados.humans.destroy",
-          "path": "humans/{uuid}",
+          "id": "arvados.keep_services.destroy",
+          "path": "keep_services/{uuid}",
           "httpMethod": "DELETE",
-          "description": "destroy humans",
+          "description": "destroy keep_services",
           "parameters": {
             "uuid": {
               "type": "string",
@@ -3271,7 +3284,7 @@
             }
           },
           "response": {
-            "$ref": "Human"
+            "$ref": "KeepService"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
@@ -3279,17 +3292,17 @@
         }
       }
     },
-    "jobs": {
+    "links": {
       "methods": {
         "get": {
-          "id": "arvados.jobs.get",
-          "path": "jobs/{uuid}",
+          "id": "arvados.links.get",
+          "path": "links/{uuid}",
           "httpMethod": "GET",
-          "description": "Gets a Job's metadata by UUID.",
+          "description": "Gets a Link's metadata by UUID.",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the Job in question.",
+              "description": "The UUID of the Link in question.",
               "required": true,
               "location": "path"
             }
@@ -3298,7 +3311,7 @@
             "uuid"
           ],
           "response": {
-            "$ref": "Job"
+            "$ref": "Link"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
@@ -3306,10 +3319,10 @@
           ]
         },
         "index": {
-          "id": "arvados.jobs.list",
-          "path": "jobs",
+          "id": "arvados.links.list",
+          "path": "links",
           "httpMethod": "GET",
-          "description": "List Jobs.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Jobs. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#jobList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
+          "description": "List Links.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Links. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#linkList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
           "parameters": {
             "filters": {
               "type": "array",
@@ -3377,7 +3390,7 @@
             }
           },
           "response": {
-            "$ref": "JobList"
+            "$ref": "LinkList"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
@@ -3385,10 +3398,10 @@
           ]
         },
         "create": {
-          "id": "arvados.jobs.create",
-          "path": "jobs",
+          "id": "arvados.links.create",
+          "path": "links",
           "httpMethod": "POST",
-          "description": "Create a new Job.",
+          "description": "Create a new Link.",
           "parameters": {
             "select": {
               "type": "array",
@@ -3408,57 +3421,32 @@
               "description": "Create object on a remote federated cluster instead of the current one.",
               "location": "query",
               "required": false
-            },
-            "find_or_create": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "minimum_script_version": {
-              "type": "string",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "exclude_script_versions": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
             }
           },
           "request": {
             "required": true,
             "properties": {
-              "job": {
-                "$ref": "Job"
+              "link": {
+                "$ref": "Link"
               }
             }
           },
           "response": {
-            "$ref": "Job"
+            "$ref": "Link"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "update": {
-          "id": "arvados.jobs.update",
-          "path": "jobs/{uuid}",
+          "id": "arvados.links.update",
+          "path": "links/{uuid}",
           "httpMethod": "PUT",
-          "description": "Update attributes of an existing Job.",
+          "description": "Update attributes of an existing Link.",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the Job in question.",
+              "description": "The UUID of the Link in question.",
               "required": true,
               "location": "path"
             },
@@ -3472,43 +3460,43 @@
           "request": {
             "required": true,
             "properties": {
-              "job": {
-                "$ref": "Job"
+              "link": {
+                "$ref": "Link"
               }
             }
           },
           "response": {
-            "$ref": "Job"
+            "$ref": "Link"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "delete": {
-          "id": "arvados.jobs.delete",
-          "path": "jobs/{uuid}",
+          "id": "arvados.links.delete",
+          "path": "links/{uuid}",
           "httpMethod": "DELETE",
-          "description": "Delete an existing Job.",
+          "description": "Delete an existing Link.",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the Job in question.",
+              "description": "The UUID of the Link in question.",
               "required": true,
               "location": "path"
             }
           },
           "response": {
-            "$ref": "Job"
+            "$ref": "Link"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
-        "queue": {
-          "id": "arvados.jobs.queue",
-          "path": "jobs/queue",
+        "list": {
+          "id": "arvados.links.list",
+          "path": "links",
           "httpMethod": "GET",
-          "description": "queue jobs",
+          "description": "List Links.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Links. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#linkList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
           "parameters": {
             "filters": {
               "type": "array",
@@ -3576,175 +3564,64 @@
             }
           },
           "response": {
-            "$ref": "Job"
+            "$ref": "LinkList"
           },
           "scopes": [
-            "https://api.arvados.org/auth/arvados"
+            "https://api.arvados.org/auth/arvados",
+            "https://api.arvados.org/auth/arvados.readonly"
           ]
         },
-        "queue_size": {
-          "id": "arvados.jobs.queue_size",
-          "path": "jobs/queue_size",
+        "show": {
+          "id": "arvados.links.show",
+          "path": "links/{uuid}",
           "httpMethod": "GET",
-          "description": "queue_size jobs",
-          "parameters": {},
-          "response": {
-            "$ref": "Job"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "cancel": {
-          "id": "arvados.jobs.cancel",
-          "path": "jobs/{uuid}/cancel",
-          "httpMethod": "POST",
-          "description": "cancel jobs",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "Job"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "lock": {
-          "id": "arvados.jobs.lock",
-          "path": "jobs/{uuid}/lock",
-          "httpMethod": "POST",
-          "description": "lock jobs",
+          "description": "show links",
           "parameters": {
             "uuid": {
               "type": "string",
               "description": "",
               "required": true,
               "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "Job"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "list": {
-          "id": "arvados.jobs.list",
-          "path": "jobs",
-          "httpMethod": "GET",
-          "description": "List Jobs.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Jobs. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#jobList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
             },
             "select": {
               "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
+              "description": "Attributes of the object to return in the response.",
               "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
               "location": "query"
             }
           },
           "response": {
-            "$ref": "JobList"
+            "$ref": "Link"
           },
           "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
+            "https://api.arvados.org/auth/arvados"
           ]
         },
-        "show": {
-          "id": "arvados.jobs.show",
-          "path": "jobs/{uuid}",
-          "httpMethod": "GET",
-          "description": "show jobs",
+        "destroy": {
+          "id": "arvados.links.destroy",
+          "path": "links/{uuid}",
+          "httpMethod": "DELETE",
+          "description": "destroy links",
           "parameters": {
             "uuid": {
               "type": "string",
               "description": "",
               "required": true,
               "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the object to return in the response.",
-              "required": false,
-              "location": "query"
             }
           },
           "response": {
-            "$ref": "Job"
+            "$ref": "Link"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
-        "destroy": {
-          "id": "arvados.jobs.destroy",
-          "path": "jobs/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "destroy jobs",
+        "get_permissions": {
+          "id": "arvados.links.get_permissions",
+          "path": "permissions/{uuid}",
+          "httpMethod": "GET",
+          "description": "get_permissions links",
           "parameters": {
             "uuid": {
               "type": "string",
@@ -3754,7 +3631,7 @@
             }
           },
           "response": {
-            "$ref": "Job"
+            "$ref": "Link"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
@@ -3762,17 +3639,17 @@
         }
       }
     },
-    "job_tasks": {
+    "logs": {
       "methods": {
         "get": {
-          "id": "arvados.job_tasks.get",
-          "path": "job_tasks/{uuid}",
+          "id": "arvados.logs.get",
+          "path": "logs/{uuid}",
           "httpMethod": "GET",
-          "description": "Gets a JobTask's metadata by UUID.",
+          "description": "Gets a Log's metadata by UUID.",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the JobTask in question.",
+              "description": "The UUID of the Log in question.",
               "required": true,
               "location": "path"
             }
@@ -3781,7 +3658,7 @@
             "uuid"
           ],
           "response": {
-            "$ref": "JobTask"
+            "$ref": "Log"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
@@ -3789,10 +3666,10 @@
           ]
         },
         "index": {
-          "id": "arvados.job_tasks.list",
-          "path": "job_tasks",
+          "id": "arvados.logs.list",
+          "path": "logs",
           "httpMethod": "GET",
-          "description": "List JobTasks.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching JobTasks. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#jobTaskList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
+          "description": "List Logs.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Logs. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#logList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
           "parameters": {
             "filters": {
               "type": "array",
@@ -3860,7 +3737,7 @@
             }
           },
           "response": {
-            "$ref": "JobTaskList"
+            "$ref": "LogList"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
@@ -3868,10 +3745,10 @@
           ]
         },
         "create": {
-          "id": "arvados.job_tasks.create",
-          "path": "job_tasks",
+          "id": "arvados.logs.create",
+          "path": "logs",
           "httpMethod": "POST",
-          "description": "Create a new JobTask.",
+          "description": "Create a new Log.",
           "parameters": {
             "select": {
               "type": "array",
@@ -3896,27 +3773,27 @@
           "request": {
             "required": true,
             "properties": {
-              "job_task": {
-                "$ref": "JobTask"
+              "log": {
+                "$ref": "Log"
               }
             }
           },
           "response": {
-            "$ref": "JobTask"
+            "$ref": "Log"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "update": {
-          "id": "arvados.job_tasks.update",
-          "path": "job_tasks/{uuid}",
+          "id": "arvados.logs.update",
+          "path": "logs/{uuid}",
           "httpMethod": "PUT",
-          "description": "Update attributes of an existing JobTask.",
+          "description": "Update attributes of an existing Log.",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the JobTask in question.",
+              "description": "The UUID of the Log in question.",
               "required": true,
               "location": "path"
             },
@@ -3930,43 +3807,43 @@
           "request": {
             "required": true,
             "properties": {
-              "job_task": {
-                "$ref": "JobTask"
-              }
+              "log": {
+                "$ref": "Log"
+              }
             }
           },
           "response": {
-            "$ref": "JobTask"
+            "$ref": "Log"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "delete": {
-          "id": "arvados.job_tasks.delete",
-          "path": "job_tasks/{uuid}",
+          "id": "arvados.logs.delete",
+          "path": "logs/{uuid}",
           "httpMethod": "DELETE",
-          "description": "Delete an existing JobTask.",
+          "description": "Delete an existing Log.",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the JobTask in question.",
+              "description": "The UUID of the Log in question.",
               "required": true,
               "location": "path"
             }
           },
           "response": {
-            "$ref": "JobTask"
+            "$ref": "Log"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "list": {
-          "id": "arvados.job_tasks.list",
-          "path": "job_tasks",
+          "id": "arvados.logs.list",
+          "path": "logs",
           "httpMethod": "GET",
-          "description": "List JobTasks.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching JobTasks. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#jobTaskList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
+          "description": "List Logs.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Logs. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#logList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
           "parameters": {
             "filters": {
               "type": "array",
@@ -4034,7 +3911,7 @@
             }
           },
           "response": {
-            "$ref": "JobTaskList"
+            "$ref": "LogList"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
@@ -4042,10 +3919,10 @@
           ]
         },
         "show": {
-          "id": "arvados.job_tasks.show",
-          "path": "job_tasks/{uuid}",
+          "id": "arvados.logs.show",
+          "path": "logs/{uuid}",
           "httpMethod": "GET",
-          "description": "show job_tasks",
+          "description": "show logs",
           "parameters": {
             "uuid": {
               "type": "string",
@@ -4061,17 +3938,17 @@
             }
           },
           "response": {
-            "$ref": "JobTask"
+            "$ref": "Log"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "destroy": {
-          "id": "arvados.job_tasks.destroy",
-          "path": "job_tasks/{uuid}",
+          "id": "arvados.logs.destroy",
+          "path": "logs/{uuid}",
           "httpMethod": "DELETE",
-          "description": "destroy job_tasks",
+          "description": "destroy logs",
           "parameters": {
             "uuid": {
               "type": "string",
@@ -4081,7 +3958,7 @@
             }
           },
           "response": {
-            "$ref": "JobTask"
+            "$ref": "Log"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
@@ -4089,17 +3966,17 @@
         }
       }
     },
-    "keep_disks": {
+    "users": {
       "methods": {
         "get": {
-          "id": "arvados.keep_disks.get",
-          "path": "keep_disks/{uuid}",
+          "id": "arvados.users.get",
+          "path": "users/{uuid}",
           "httpMethod": "GET",
-          "description": "Gets a KeepDisk's metadata by UUID.",
+          "description": "Gets a User's metadata by UUID.",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the KeepDisk in question.",
+              "description": "The UUID of the User in question.",
               "required": true,
               "location": "path"
             }
@@ -4108,7 +3985,7 @@
             "uuid"
           ],
           "response": {
-            "$ref": "KeepDisk"
+            "$ref": "User"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
@@ -4116,10 +3993,10 @@
           ]
         },
         "index": {
-          "id": "arvados.keep_disks.list",
-          "path": "keep_disks",
+          "id": "arvados.users.list",
+          "path": "users",
           "httpMethod": "GET",
-          "description": "List KeepDisks.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching KeepDisks. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#keepDiskList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
+          "description": "List Users.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Users. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#userList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
           "parameters": {
             "filters": {
               "type": "array",
@@ -4187,7 +4064,7 @@
             }
           },
           "response": {
-            "$ref": "KeepDiskList"
+            "$ref": "UserList"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
@@ -4195,10 +4072,10 @@
           ]
         },
         "create": {
-          "id": "arvados.keep_disks.create",
-          "path": "keep_disks",
+          "id": "arvados.users.create",
+          "path": "users",
           "httpMethod": "POST",
-          "description": "Create a new KeepDisk.",
+          "description": "Create a new User.",
           "parameters": {
             "select": {
               "type": "array",
@@ -4223,27 +4100,27 @@
           "request": {
             "required": true,
             "properties": {
-              "keep_disk": {
-                "$ref": "KeepDisk"
+              "user": {
+                "$ref": "User"
               }
             }
           },
           "response": {
-            "$ref": "KeepDisk"
+            "$ref": "User"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "update": {
-          "id": "arvados.keep_disks.update",
-          "path": "keep_disks/{uuid}",
+          "id": "arvados.users.update",
+          "path": "users/{uuid}",
           "httpMethod": "PUT",
-          "description": "Update attributes of an existing KeepDisk.",
+          "description": "Update attributes of an existing User.",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the KeepDisk in question.",
+              "description": "The UUID of the User in question.",
               "required": true,
               "location": "path"
             },
@@ -4252,104 +4129,211 @@
               "description": "Attributes of the updated object to return in the response.",
               "required": false,
               "location": "query"
+            },
+            "bypass_federation": {
+              "type": "boolean",
+              "required": false,
+              "default": "false",
+              "description": "",
+              "location": "query"
             }
           },
           "request": {
             "required": true,
             "properties": {
-              "keep_disk": {
-                "$ref": "KeepDisk"
+              "user": {
+                "$ref": "User"
               }
             }
           },
           "response": {
-            "$ref": "KeepDisk"
+            "$ref": "User"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "delete": {
-          "id": "arvados.keep_disks.delete",
-          "path": "keep_disks/{uuid}",
+          "id": "arvados.users.delete",
+          "path": "users/{uuid}",
           "httpMethod": "DELETE",
-          "description": "Delete an existing KeepDisk.",
+          "description": "Delete an existing User.",
+          "parameters": {
+            "uuid": {
+              "type": "string",
+              "description": "The UUID of the User in question.",
+              "required": true,
+              "location": "path"
+            }
+          },
+          "response": {
+            "$ref": "User"
+          },
+          "scopes": [
+            "https://api.arvados.org/auth/arvados"
+          ]
+        },
+        "current": {
+          "id": "arvados.users.current",
+          "path": "users/current",
+          "httpMethod": "GET",
+          "description": "current users",
+          "parameters": {},
+          "response": {
+            "$ref": "User"
+          },
+          "scopes": [
+            "https://api.arvados.org/auth/arvados"
+          ]
+        },
+        "system": {
+          "id": "arvados.users.system",
+          "path": "users/system",
+          "httpMethod": "GET",
+          "description": "system users",
+          "parameters": {},
+          "response": {
+            "$ref": "User"
+          },
+          "scopes": [
+            "https://api.arvados.org/auth/arvados"
+          ]
+        },
+        "activate": {
+          "id": "arvados.users.activate",
+          "path": "users/{uuid}/activate",
+          "httpMethod": "POST",
+          "description": "activate users",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the KeepDisk in question.",
+              "description": "",
               "required": true,
               "location": "path"
             }
           },
           "response": {
-            "$ref": "KeepDisk"
+            "$ref": "User"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
-        "ping": {
-          "id": "arvados.keep_disks.ping",
-          "path": "keep_disks/ping",
+        "setup": {
+          "id": "arvados.users.setup",
+          "path": "users/setup",
           "httpMethod": "POST",
-          "description": "ping keep_disks",
+          "description": "setup users",
           "parameters": {
             "uuid": {
+              "type": "string",
+              "required": false,
+              "description": "",
+              "location": "query"
+            },
+            "user": {
+              "type": "object",
               "required": false,
+              "description": "",
+              "location": "query"
+            },
+            "repo_name": {
               "type": "string",
+              "required": false,
               "description": "",
               "location": "query"
             },
-            "ping_secret": {
-              "required": true,
+            "vm_uuid": {
               "type": "string",
+              "required": false,
               "description": "",
               "location": "query"
             },
-            "node_uuid": {
+            "send_notification_email": {
+              "type": "boolean",
               "required": false,
+              "default": "false",
+              "description": "",
+              "location": "query"
+            }
+          },
+          "response": {
+            "$ref": "User"
+          },
+          "scopes": [
+            "https://api.arvados.org/auth/arvados"
+          ]
+        },
+        "unsetup": {
+          "id": "arvados.users.unsetup",
+          "path": "users/{uuid}/unsetup",
+          "httpMethod": "POST",
+          "description": "unsetup users",
+          "parameters": {
+            "uuid": {
+              "type": "string",
+              "description": "",
+              "required": true,
+              "location": "path"
+            }
+          },
+          "response": {
+            "$ref": "User"
+          },
+          "scopes": [
+            "https://api.arvados.org/auth/arvados"
+          ]
+        },
+        "merge": {
+          "id": "arvados.users.merge",
+          "path": "users/merge",
+          "httpMethod": "POST",
+          "description": "merge users",
+          "parameters": {
+            "new_owner_uuid": {
               "type": "string",
+              "required": true,
               "description": "",
               "location": "query"
             },
-            "filesystem_uuid": {
-              "required": false,
+            "new_user_token": {
               "type": "string",
+              "required": false,
               "description": "",
               "location": "query"
             },
-            "service_host": {
+            "redirect_to_new_user": {
+              "type": "boolean",
               "required": false,
-              "type": "string",
+              "default": "false",
               "description": "",
               "location": "query"
             },
-            "service_port": {
-              "required": true,
+            "old_user_uuid": {
               "type": "string",
+              "required": false,
               "description": "",
               "location": "query"
             },
-            "service_ssl_flag": {
-              "required": true,
+            "new_user_uuid": {
               "type": "string",
+              "required": false,
               "description": "",
               "location": "query"
             }
           },
           "response": {
-            "$ref": "KeepDisk"
+            "$ref": "User"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "list": {
-          "id": "arvados.keep_disks.list",
-          "path": "keep_disks",
+          "id": "arvados.users.list",
+          "path": "users",
           "httpMethod": "GET",
-          "description": "List KeepDisks.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching KeepDisks. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#keepDiskList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
+          "description": "List Users.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Users. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#userList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
           "parameters": {
             "filters": {
               "type": "array",
@@ -4417,7 +4401,7 @@
             }
           },
           "response": {
-            "$ref": "KeepDiskList"
+            "$ref": "UserList"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
@@ -4425,10 +4409,10 @@
           ]
         },
         "show": {
-          "id": "arvados.keep_disks.show",
-          "path": "keep_disks/{uuid}",
+          "id": "arvados.users.show",
+          "path": "users/{uuid}",
           "httpMethod": "GET",
-          "description": "show keep_disks",
+          "description": "show users",
           "parameters": {
             "uuid": {
               "type": "string",
@@ -4444,17 +4428,17 @@
             }
           },
           "response": {
-            "$ref": "KeepDisk"
+            "$ref": "User"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "destroy": {
-          "id": "arvados.keep_disks.destroy",
-          "path": "keep_disks/{uuid}",
+          "id": "arvados.users.destroy",
+          "path": "users/{uuid}",
           "httpMethod": "DELETE",
-          "description": "destroy keep_disks",
+          "description": "destroy users",
           "parameters": {
             "uuid": {
               "type": "string",
@@ -4464,7 +4448,7 @@
             }
           },
           "response": {
-            "$ref": "KeepDisk"
+            "$ref": "User"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
@@ -4472,17 +4456,17 @@
         }
       }
     },
-    "keep_services": {
+    "user_agreements": {
       "methods": {
         "get": {
-          "id": "arvados.keep_services.get",
-          "path": "keep_services/{uuid}",
+          "id": "arvados.user_agreements.get",
+          "path": "user_agreements/{uuid}",
           "httpMethod": "GET",
-          "description": "Gets a KeepService's metadata by UUID.",
+          "description": "Gets a UserAgreement's metadata by UUID.",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the KeepService in question.",
+              "description": "The UUID of the UserAgreement in question.",
               "required": true,
               "location": "path"
             }
@@ -4491,7 +4475,7 @@
             "uuid"
           ],
           "response": {
-            "$ref": "KeepService"
+            "$ref": "UserAgreement"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
@@ -4499,10 +4483,10 @@
           ]
         },
         "index": {
-          "id": "arvados.keep_services.list",
-          "path": "keep_services",
+          "id": "arvados.user_agreements.list",
+          "path": "user_agreements",
           "httpMethod": "GET",
-          "description": "List KeepServices.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching KeepServices. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#keepServiceList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
+          "description": "List UserAgreements.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching UserAgreements. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#userAgreementList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
           "parameters": {
             "filters": {
               "type": "array",
@@ -4570,7 +4554,7 @@
             }
           },
           "response": {
-            "$ref": "KeepServiceList"
+            "$ref": "UserAgreementList"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
@@ -4578,10 +4562,10 @@
           ]
         },
         "create": {
-          "id": "arvados.keep_services.create",
-          "path": "keep_services",
+          "id": "arvados.user_agreements.create",
+          "path": "user_agreements",
           "httpMethod": "POST",
-          "description": "Create a new KeepService.",
+          "description": "Create a new UserAgreement.",
           "parameters": {
             "select": {
               "type": "array",
@@ -4606,27 +4590,27 @@
           "request": {
             "required": true,
             "properties": {
-              "keep_service": {
-                "$ref": "KeepService"
+              "user_agreement": {
+                "$ref": "UserAgreement"
               }
             }
           },
           "response": {
-            "$ref": "KeepService"
+            "$ref": "UserAgreement"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "update": {
-          "id": "arvados.keep_services.update",
-          "path": "keep_services/{uuid}",
+          "id": "arvados.user_agreements.update",
+          "path": "user_agreements/{uuid}",
           "httpMethod": "PUT",
-          "description": "Update attributes of an existing KeepService.",
+          "description": "Update attributes of an existing UserAgreement.",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the KeepService in question.",
+              "description": "The UUID of the UserAgreement in question.",
               "required": true,
               "location": "path"
             },
@@ -4640,56 +4624,69 @@
           "request": {
             "required": true,
             "properties": {
-              "keep_service": {
-                "$ref": "KeepService"
+              "user_agreement": {
+                "$ref": "UserAgreement"
               }
             }
           },
           "response": {
-            "$ref": "KeepService"
+            "$ref": "UserAgreement"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "delete": {
-          "id": "arvados.keep_services.delete",
-          "path": "keep_services/{uuid}",
+          "id": "arvados.user_agreements.delete",
+          "path": "user_agreements/{uuid}",
           "httpMethod": "DELETE",
-          "description": "Delete an existing KeepService.",
+          "description": "Delete an existing UserAgreement.",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the KeepService in question.",
+              "description": "The UUID of the UserAgreement in question.",
               "required": true,
               "location": "path"
             }
           },
           "response": {
-            "$ref": "KeepService"
+            "$ref": "UserAgreement"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
-        "accessible": {
-          "id": "arvados.keep_services.accessible",
-          "path": "keep_services/accessible",
+        "signatures": {
+          "id": "arvados.user_agreements.signatures",
+          "path": "user_agreements/signatures",
           "httpMethod": "GET",
-          "description": "accessible keep_services",
+          "description": "signatures user_agreements",
           "parameters": {},
           "response": {
-            "$ref": "KeepService"
+            "$ref": "UserAgreement"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
-        "list": {
-          "id": "arvados.keep_services.list",
-          "path": "keep_services",
+        "sign": {
+          "id": "arvados.user_agreements.sign",
+          "path": "user_agreements/sign",
+          "httpMethod": "POST",
+          "description": "sign user_agreements",
+          "parameters": {},
+          "response": {
+            "$ref": "UserAgreement"
+          },
+          "scopes": [
+            "https://api.arvados.org/auth/arvados"
+          ]
+        },
+        "list": {
+          "id": "arvados.user_agreements.list",
+          "path": "user_agreements",
           "httpMethod": "GET",
-          "description": "List KeepServices.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching KeepServices. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#keepServiceList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
+          "description": "List UserAgreements.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching UserAgreements. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#userAgreementList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
           "parameters": {
             "filters": {
               "type": "array",
@@ -4757,18 +4754,31 @@
             }
           },
           "response": {
-            "$ref": "KeepServiceList"
+            "$ref": "UserAgreementList"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
             "https://api.arvados.org/auth/arvados.readonly"
           ]
         },
+        "new": {
+          "id": "arvados.user_agreements.new",
+          "path": "user_agreements/new",
+          "httpMethod": "GET",
+          "description": "new user_agreements",
+          "parameters": {},
+          "response": {
+            "$ref": "UserAgreement"
+          },
+          "scopes": [
+            "https://api.arvados.org/auth/arvados"
+          ]
+        },
         "show": {
-          "id": "arvados.keep_services.show",
-          "path": "keep_services/{uuid}",
+          "id": "arvados.user_agreements.show",
+          "path": "user_agreements/{uuid}",
           "httpMethod": "GET",
-          "description": "show keep_services",
+          "description": "show user_agreements",
           "parameters": {
             "uuid": {
               "type": "string",
@@ -4784,17 +4794,17 @@
             }
           },
           "response": {
-            "$ref": "KeepService"
+            "$ref": "UserAgreement"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "destroy": {
-          "id": "arvados.keep_services.destroy",
-          "path": "keep_services/{uuid}",
+          "id": "arvados.user_agreements.destroy",
+          "path": "user_agreements/{uuid}",
           "httpMethod": "DELETE",
-          "description": "destroy keep_services",
+          "description": "destroy user_agreements",
           "parameters": {
             "uuid": {
               "type": "string",
@@ -4804,7 +4814,7 @@
             }
           },
           "response": {
-            "$ref": "KeepService"
+            "$ref": "UserAgreement"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
@@ -4812,17 +4822,17 @@
         }
       }
     },
-    "links": {
+    "virtual_machines": {
       "methods": {
         "get": {
-          "id": "arvados.links.get",
-          "path": "links/{uuid}",
+          "id": "arvados.virtual_machines.get",
+          "path": "virtual_machines/{uuid}",
           "httpMethod": "GET",
-          "description": "Gets a Link's metadata by UUID.",
+          "description": "Gets a VirtualMachine's metadata by UUID.",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the Link in question.",
+              "description": "The UUID of the VirtualMachine in question.",
               "required": true,
               "location": "path"
             }
@@ -4831,7 +4841,7 @@
             "uuid"
           ],
           "response": {
-            "$ref": "Link"
+            "$ref": "VirtualMachine"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
@@ -4839,10 +4849,10 @@
           ]
         },
         "index": {
-          "id": "arvados.links.list",
-          "path": "links",
+          "id": "arvados.virtual_machines.list",
+          "path": "virtual_machines",
           "httpMethod": "GET",
-          "description": "List Links.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Links. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#linkList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
+          "description": "List VirtualMachines.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching VirtualMachines. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#virtualMachineList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
           "parameters": {
             "filters": {
               "type": "array",
@@ -4910,7 +4920,7 @@
             }
           },
           "response": {
-            "$ref": "LinkList"
+            "$ref": "VirtualMachineList"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
@@ -4918,10 +4928,10 @@
           ]
         },
         "create": {
-          "id": "arvados.links.create",
-          "path": "links",
+          "id": "arvados.virtual_machines.create",
+          "path": "virtual_machines",
           "httpMethod": "POST",
-          "description": "Create a new Link.",
+          "description": "Create a new VirtualMachine.",
           "parameters": {
             "select": {
               "type": "array",
@@ -4946,27 +4956,27 @@
           "request": {
             "required": true,
             "properties": {
-              "link": {
-                "$ref": "Link"
+              "virtual_machine": {
+                "$ref": "VirtualMachine"
               }
             }
           },
           "response": {
-            "$ref": "Link"
+            "$ref": "VirtualMachine"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "update": {
-          "id": "arvados.links.update",
-          "path": "links/{uuid}",
+          "id": "arvados.virtual_machines.update",
+          "path": "virtual_machines/{uuid}",
           "httpMethod": "PUT",
-          "description": "Update attributes of an existing Link.",
+          "description": "Update attributes of an existing VirtualMachine.",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the Link in question.",
+              "description": "The UUID of the VirtualMachine in question.",
               "required": true,
               "location": "path"
             },
@@ -4980,43 +4990,76 @@
           "request": {
             "required": true,
             "properties": {
-              "link": {
-                "$ref": "Link"
+              "virtual_machine": {
+                "$ref": "VirtualMachine"
               }
             }
           },
           "response": {
-            "$ref": "Link"
+            "$ref": "VirtualMachine"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "delete": {
-          "id": "arvados.links.delete",
-          "path": "links/{uuid}",
+          "id": "arvados.virtual_machines.delete",
+          "path": "virtual_machines/{uuid}",
           "httpMethod": "DELETE",
-          "description": "Delete an existing Link.",
+          "description": "Delete an existing VirtualMachine.",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the Link in question.",
+              "description": "The UUID of the VirtualMachine in question.",
               "required": true,
               "location": "path"
             }
           },
           "response": {
-            "$ref": "Link"
+            "$ref": "VirtualMachine"
+          },
+          "scopes": [
+            "https://api.arvados.org/auth/arvados"
+          ]
+        },
+        "logins": {
+          "id": "arvados.virtual_machines.logins",
+          "path": "virtual_machines/{uuid}/logins",
+          "httpMethod": "GET",
+          "description": "logins virtual_machines",
+          "parameters": {
+            "uuid": {
+              "type": "string",
+              "description": "",
+              "required": true,
+              "location": "path"
+            }
+          },
+          "response": {
+            "$ref": "VirtualMachine"
+          },
+          "scopes": [
+            "https://api.arvados.org/auth/arvados"
+          ]
+        },
+        "get_all_logins": {
+          "id": "arvados.virtual_machines.get_all_logins",
+          "path": "virtual_machines/get_all_logins",
+          "httpMethod": "GET",
+          "description": "get_all_logins virtual_machines",
+          "parameters": {},
+          "response": {
+            "$ref": "VirtualMachine"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "list": {
-          "id": "arvados.links.list",
-          "path": "links",
+          "id": "arvados.virtual_machines.list",
+          "path": "virtual_machines",
           "httpMethod": "GET",
-          "description": "List Links.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Links. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#linkList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
+          "description": "List VirtualMachines.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching VirtualMachines. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#virtualMachineList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
           "parameters": {
             "filters": {
               "type": "array",
@@ -5084,7 +5127,7 @@
             }
           },
           "response": {
-            "$ref": "LinkList"
+            "$ref": "VirtualMachineList"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
@@ -5092,10 +5135,10 @@
           ]
         },
         "show": {
-          "id": "arvados.links.show",
-          "path": "links/{uuid}",
+          "id": "arvados.virtual_machines.show",
+          "path": "virtual_machines/{uuid}",
           "httpMethod": "GET",
-          "description": "show links",
+          "description": "show virtual_machines",
           "parameters": {
             "uuid": {
               "type": "string",
@@ -5111,37 +5154,17 @@
             }
           },
           "response": {
-            "$ref": "Link"
+            "$ref": "VirtualMachine"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "destroy": {
-          "id": "arvados.links.destroy",
-          "path": "links/{uuid}",
+          "id": "arvados.virtual_machines.destroy",
+          "path": "virtual_machines/{uuid}",
           "httpMethod": "DELETE",
-          "description": "destroy links",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "Link"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "get_permissions": {
-          "id": "arvados.links.get_permissions",
-          "path": "permissions/{uuid}",
-          "httpMethod": "GET",
-          "description": "get_permissions links",
+          "description": "destroy virtual_machines",
           "parameters": {
             "uuid": {
               "type": "string",
@@ -5151,7 +5174,7 @@
             }
           },
           "response": {
-            "$ref": "Link"
+            "$ref": "VirtualMachine"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
@@ -5159,17 +5182,17 @@
         }
       }
     },
-    "logs": {
+    "workflows": {
       "methods": {
         "get": {
-          "id": "arvados.logs.get",
-          "path": "logs/{uuid}",
+          "id": "arvados.workflows.get",
+          "path": "workflows/{uuid}",
           "httpMethod": "GET",
-          "description": "Gets a Log's metadata by UUID.",
+          "description": "Gets a Workflow's metadata by UUID.",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the Log in question.",
+              "description": "The UUID of the Workflow in question.",
               "required": true,
               "location": "path"
             }
@@ -5178,7 +5201,7 @@
             "uuid"
           ],
           "response": {
-            "$ref": "Log"
+            "$ref": "Workflow"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
@@ -5186,10 +5209,10 @@
           ]
         },
         "index": {
-          "id": "arvados.logs.list",
-          "path": "logs",
+          "id": "arvados.workflows.list",
+          "path": "workflows",
           "httpMethod": "GET",
-          "description": "List Logs.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Logs. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#logList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
+          "description": "List Workflows.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Workflows. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#workflowList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
           "parameters": {
             "filters": {
               "type": "array",
@@ -5257,7 +5280,7 @@
             }
           },
           "response": {
-            "$ref": "LogList"
+            "$ref": "WorkflowList"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
@@ -5265,10 +5288,10 @@
           ]
         },
         "create": {
-          "id": "arvados.logs.create",
-          "path": "logs",
+          "id": "arvados.workflows.create",
+          "path": "workflows",
           "httpMethod": "POST",
-          "description": "Create a new Log.",
+          "description": "Create a new Workflow.",
           "parameters": {
             "select": {
               "type": "array",
@@ -5293,27 +5316,27 @@
           "request": {
             "required": true,
             "properties": {
-              "log": {
-                "$ref": "Log"
+              "workflow": {
+                "$ref": "Workflow"
               }
             }
           },
           "response": {
-            "$ref": "Log"
+            "$ref": "Workflow"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "update": {
-          "id": "arvados.logs.update",
-          "path": "logs/{uuid}",
+          "id": "arvados.workflows.update",
+          "path": "workflows/{uuid}",
           "httpMethod": "PUT",
-          "description": "Update attributes of an existing Log.",
+          "description": "Update attributes of an existing Workflow.",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the Log in question.",
+              "description": "The UUID of the Workflow in question.",
               "required": true,
               "location": "path"
             },
@@ -5327,43 +5350,43 @@
           "request": {
             "required": true,
             "properties": {
-              "log": {
-                "$ref": "Log"
+              "workflow": {
+                "$ref": "Workflow"
               }
             }
           },
           "response": {
-            "$ref": "Log"
+            "$ref": "Workflow"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "delete": {
-          "id": "arvados.logs.delete",
-          "path": "logs/{uuid}",
+          "id": "arvados.workflows.delete",
+          "path": "workflows/{uuid}",
           "httpMethod": "DELETE",
-          "description": "Delete an existing Log.",
+          "description": "Delete an existing Workflow.",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the Log in question.",
+              "description": "The UUID of the Workflow in question.",
               "required": true,
               "location": "path"
             }
           },
           "response": {
-            "$ref": "Log"
+            "$ref": "Workflow"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "list": {
-          "id": "arvados.logs.list",
-          "path": "logs",
+          "id": "arvados.workflows.list",
+          "path": "workflows",
           "httpMethod": "GET",
-          "description": "List Logs.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Logs. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#logList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
+          "description": "List Workflows.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Workflows. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#workflowList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
           "parameters": {
             "filters": {
               "type": "array",
@@ -5431,7 +5454,7 @@
             }
           },
           "response": {
-            "$ref": "LogList"
+            "$ref": "WorkflowList"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
@@ -5439,10 +5462,10 @@
           ]
         },
         "show": {
-          "id": "arvados.logs.show",
-          "path": "logs/{uuid}",
+          "id": "arvados.workflows.show",
+          "path": "workflows/{uuid}",
           "httpMethod": "GET",
-          "description": "show logs",
+          "description": "show workflows",
           "parameters": {
             "uuid": {
               "type": "string",
@@ -5458,4481 +5481,97 @@
             }
           },
           "response": {
-            "$ref": "Log"
+            "$ref": "Workflow"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "destroy": {
-          "id": "arvados.logs.destroy",
-          "path": "logs/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "destroy logs",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "Log"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        }
-      }
-    },
-    "nodes": {
-      "methods": {
-        "get": {
-          "id": "arvados.nodes.get",
-          "path": "nodes/{uuid}",
-          "httpMethod": "GET",
-          "description": "Gets a Node's metadata by UUID.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the Node in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "parameterOrder": [
-            "uuid"
-          ],
-          "response": {
-            "$ref": "Node"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "index": {
-          "id": "arvados.nodes.list",
-          "path": "nodes",
-          "httpMethod": "GET",
-          "description": "List Nodes.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Nodes. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#nodeList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "NodeList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "create": {
-          "id": "arvados.nodes.create",
-          "path": "nodes",
-          "httpMethod": "POST",
-          "description": "Create a new Node.",
-          "parameters": {
-            "select": {
-              "type": "array",
-              "description": "Attributes of the new object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "ensure_unique_name": {
-              "type": "boolean",
-              "description": "Adjust name to ensure uniqueness instead of returning an error on (owner_uuid, name) collision.",
-              "location": "query",
-              "required": false,
-              "default": "false"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "Create object on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "assign_slot": {
-              "required": false,
-              "type": "boolean",
-              "description": "assign slot and hostname",
-              "location": "query"
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "node": {
-                "$ref": "Node"
-              }
-            }
-          },
-          "response": {
-            "$ref": "Node"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "update": {
-          "id": "arvados.nodes.update",
-          "path": "nodes/{uuid}",
-          "httpMethod": "PUT",
-          "description": "Update attributes of an existing Node.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the Node in question.",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the updated object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "assign_slot": {
-              "required": false,
-              "type": "boolean",
-              "description": "assign slot and hostname",
-              "location": "query"
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "node": {
-                "$ref": "Node"
-              }
-            }
-          },
-          "response": {
-            "$ref": "Node"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "delete": {
-          "id": "arvados.nodes.delete",
-          "path": "nodes/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "Delete an existing Node.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the Node in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "Node"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "ping": {
-          "id": "arvados.nodes.ping",
-          "path": "nodes/{uuid}/ping",
-          "httpMethod": "POST",
-          "description": "ping nodes",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            },
-            "ping_secret": {
-              "required": true,
-              "type": "string",
-              "description": "",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "Node"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "list": {
-          "id": "arvados.nodes.list",
-          "path": "nodes",
-          "httpMethod": "GET",
-          "description": "List Nodes.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Nodes. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#nodeList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "NodeList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "show": {
-          "id": "arvados.nodes.show",
-          "path": "nodes/{uuid}",
-          "httpMethod": "GET",
-          "description": "show nodes",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the object to return in the response.",
-              "required": false,
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "Node"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "destroy": {
-          "id": "arvados.nodes.destroy",
-          "path": "nodes/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "destroy nodes",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "Node"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        }
-      }
-    },
-    "pipeline_instances": {
-      "methods": {
-        "get": {
-          "id": "arvados.pipeline_instances.get",
-          "path": "pipeline_instances/{uuid}",
-          "httpMethod": "GET",
-          "description": "Gets a PipelineInstance's metadata by UUID.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the PipelineInstance in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "parameterOrder": [
-            "uuid"
-          ],
-          "response": {
-            "$ref": "PipelineInstance"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "index": {
-          "id": "arvados.pipeline_instances.list",
-          "path": "pipeline_instances",
-          "httpMethod": "GET",
-          "description": "List PipelineInstances.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching PipelineInstances. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#pipelineInstanceList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "PipelineInstanceList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "create": {
-          "id": "arvados.pipeline_instances.create",
-          "path": "pipeline_instances",
-          "httpMethod": "POST",
-          "description": "Create a new PipelineInstance.",
-          "parameters": {
-            "select": {
-              "type": "array",
-              "description": "Attributes of the new object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "ensure_unique_name": {
-              "type": "boolean",
-              "description": "Adjust name to ensure uniqueness instead of returning an error on (owner_uuid, name) collision.",
-              "location": "query",
-              "required": false,
-              "default": "false"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "Create object on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "pipeline_instance": {
-                "$ref": "PipelineInstance"
-              }
-            }
-          },
-          "response": {
-            "$ref": "PipelineInstance"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "update": {
-          "id": "arvados.pipeline_instances.update",
-          "path": "pipeline_instances/{uuid}",
-          "httpMethod": "PUT",
-          "description": "Update attributes of an existing PipelineInstance.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the PipelineInstance in question.",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the updated object to return in the response.",
-              "required": false,
-              "location": "query"
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "pipeline_instance": {
-                "$ref": "PipelineInstance"
-              }
-            }
-          },
-          "response": {
-            "$ref": "PipelineInstance"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "delete": {
-          "id": "arvados.pipeline_instances.delete",
-          "path": "pipeline_instances/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "Delete an existing PipelineInstance.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the PipelineInstance in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "PipelineInstance"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "cancel": {
-          "id": "arvados.pipeline_instances.cancel",
-          "path": "pipeline_instances/{uuid}/cancel",
-          "httpMethod": "POST",
-          "description": "cancel pipeline_instances",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "PipelineInstance"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "list": {
-          "id": "arvados.pipeline_instances.list",
-          "path": "pipeline_instances",
-          "httpMethod": "GET",
-          "description": "List PipelineInstances.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching PipelineInstances. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#pipelineInstanceList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "PipelineInstanceList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "show": {
-          "id": "arvados.pipeline_instances.show",
-          "path": "pipeline_instances/{uuid}",
-          "httpMethod": "GET",
-          "description": "show pipeline_instances",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the object to return in the response.",
-              "required": false,
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "PipelineInstance"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "destroy": {
-          "id": "arvados.pipeline_instances.destroy",
-          "path": "pipeline_instances/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "destroy pipeline_instances",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "PipelineInstance"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        }
-      }
-    },
-    "pipeline_templates": {
-      "methods": {
-        "get": {
-          "id": "arvados.pipeline_templates.get",
-          "path": "pipeline_templates/{uuid}",
-          "httpMethod": "GET",
-          "description": "Gets a PipelineTemplate's metadata by UUID.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the PipelineTemplate in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "parameterOrder": [
-            "uuid"
-          ],
-          "response": {
-            "$ref": "PipelineTemplate"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "index": {
-          "id": "arvados.pipeline_templates.list",
-          "path": "pipeline_templates",
-          "httpMethod": "GET",
-          "description": "List PipelineTemplates.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching PipelineTemplates. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#pipelineTemplateList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "PipelineTemplateList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "create": {
-          "id": "arvados.pipeline_templates.create",
-          "path": "pipeline_templates",
-          "httpMethod": "POST",
-          "description": "Create a new PipelineTemplate.",
-          "parameters": {
-            "select": {
-              "type": "array",
-              "description": "Attributes of the new object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "ensure_unique_name": {
-              "type": "boolean",
-              "description": "Adjust name to ensure uniqueness instead of returning an error on (owner_uuid, name) collision.",
-              "location": "query",
-              "required": false,
-              "default": "false"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "Create object on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "pipeline_template": {
-                "$ref": "PipelineTemplate"
-              }
-            }
-          },
-          "response": {
-            "$ref": "PipelineTemplate"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "update": {
-          "id": "arvados.pipeline_templates.update",
-          "path": "pipeline_templates/{uuid}",
-          "httpMethod": "PUT",
-          "description": "Update attributes of an existing PipelineTemplate.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the PipelineTemplate in question.",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the updated object to return in the response.",
-              "required": false,
-              "location": "query"
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "pipeline_template": {
-                "$ref": "PipelineTemplate"
-              }
-            }
-          },
-          "response": {
-            "$ref": "PipelineTemplate"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "delete": {
-          "id": "arvados.pipeline_templates.delete",
-          "path": "pipeline_templates/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "Delete an existing PipelineTemplate.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the PipelineTemplate in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "PipelineTemplate"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "list": {
-          "id": "arvados.pipeline_templates.list",
-          "path": "pipeline_templates",
-          "httpMethod": "GET",
-          "description": "List PipelineTemplates.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching PipelineTemplates. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#pipelineTemplateList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "PipelineTemplateList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "show": {
-          "id": "arvados.pipeline_templates.show",
-          "path": "pipeline_templates/{uuid}",
-          "httpMethod": "GET",
-          "description": "show pipeline_templates",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the object to return in the response.",
-              "required": false,
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "PipelineTemplate"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "destroy": {
-          "id": "arvados.pipeline_templates.destroy",
-          "path": "pipeline_templates/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "destroy pipeline_templates",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "PipelineTemplate"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        }
-      }
-    },
-    "repositories": {
-      "methods": {
-        "get": {
-          "id": "arvados.repositories.get",
-          "path": "repositories/{uuid}",
-          "httpMethod": "GET",
-          "description": "Gets a Repository's metadata by UUID.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the Repository in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "parameterOrder": [
-            "uuid"
-          ],
-          "response": {
-            "$ref": "Repository"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "index": {
-          "id": "arvados.repositories.list",
-          "path": "repositories",
-          "httpMethod": "GET",
-          "description": "List Repositories.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Repositories. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#repositoryList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "RepositoryList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "create": {
-          "id": "arvados.repositories.create",
-          "path": "repositories",
-          "httpMethod": "POST",
-          "description": "Create a new Repository.",
-          "parameters": {
-            "select": {
-              "type": "array",
-              "description": "Attributes of the new object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "ensure_unique_name": {
-              "type": "boolean",
-              "description": "Adjust name to ensure uniqueness instead of returning an error on (owner_uuid, name) collision.",
-              "location": "query",
-              "required": false,
-              "default": "false"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "Create object on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "repository": {
-                "$ref": "Repository"
-              }
-            }
-          },
-          "response": {
-            "$ref": "Repository"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "update": {
-          "id": "arvados.repositories.update",
-          "path": "repositories/{uuid}",
-          "httpMethod": "PUT",
-          "description": "Update attributes of an existing Repository.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the Repository in question.",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the updated object to return in the response.",
-              "required": false,
-              "location": "query"
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "repository": {
-                "$ref": "Repository"
-              }
-            }
-          },
-          "response": {
-            "$ref": "Repository"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "delete": {
-          "id": "arvados.repositories.delete",
-          "path": "repositories/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "Delete an existing Repository.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the Repository in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "Repository"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "get_all_permissions": {
-          "id": "arvados.repositories.get_all_permissions",
-          "path": "repositories/get_all_permissions",
-          "httpMethod": "GET",
-          "description": "get_all_permissions repositories",
-          "parameters": {},
-          "response": {
-            "$ref": "Repository"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "list": {
-          "id": "arvados.repositories.list",
-          "path": "repositories",
-          "httpMethod": "GET",
-          "description": "List Repositories.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Repositories. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#repositoryList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "RepositoryList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "show": {
-          "id": "arvados.repositories.show",
-          "path": "repositories/{uuid}",
-          "httpMethod": "GET",
-          "description": "show repositories",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the object to return in the response.",
-              "required": false,
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "Repository"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "destroy": {
-          "id": "arvados.repositories.destroy",
-          "path": "repositories/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "destroy repositories",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "Repository"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        }
-      }
-    },
-    "specimens": {
-      "methods": {
-        "get": {
-          "id": "arvados.specimens.get",
-          "path": "specimens/{uuid}",
-          "httpMethod": "GET",
-          "description": "Gets a Specimen's metadata by UUID.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the Specimen in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "parameterOrder": [
-            "uuid"
-          ],
-          "response": {
-            "$ref": "Specimen"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "index": {
-          "id": "arvados.specimens.list",
-          "path": "specimens",
-          "httpMethod": "GET",
-          "description": "List Specimens.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Specimens. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#specimenList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "SpecimenList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "create": {
-          "id": "arvados.specimens.create",
-          "path": "specimens",
-          "httpMethod": "POST",
-          "description": "Create a new Specimen.",
-          "parameters": {
-            "select": {
-              "type": "array",
-              "description": "Attributes of the new object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "ensure_unique_name": {
-              "type": "boolean",
-              "description": "Adjust name to ensure uniqueness instead of returning an error on (owner_uuid, name) collision.",
-              "location": "query",
-              "required": false,
-              "default": "false"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "Create object on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "specimen": {
-                "$ref": "Specimen"
-              }
-            }
-          },
-          "response": {
-            "$ref": "Specimen"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "update": {
-          "id": "arvados.specimens.update",
-          "path": "specimens/{uuid}",
-          "httpMethod": "PUT",
-          "description": "Update attributes of an existing Specimen.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the Specimen in question.",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the updated object to return in the response.",
-              "required": false,
-              "location": "query"
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "specimen": {
-                "$ref": "Specimen"
-              }
-            }
-          },
-          "response": {
-            "$ref": "Specimen"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "delete": {
-          "id": "arvados.specimens.delete",
-          "path": "specimens/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "Delete an existing Specimen.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the Specimen in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "Specimen"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "list": {
-          "id": "arvados.specimens.list",
-          "path": "specimens",
-          "httpMethod": "GET",
-          "description": "List Specimens.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Specimens. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#specimenList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "SpecimenList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "show": {
-          "id": "arvados.specimens.show",
-          "path": "specimens/{uuid}",
-          "httpMethod": "GET",
-          "description": "show specimens",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the object to return in the response.",
-              "required": false,
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "Specimen"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "destroy": {
-          "id": "arvados.specimens.destroy",
-          "path": "specimens/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "destroy specimens",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "Specimen"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        }
-      }
-    },
-    "traits": {
-      "methods": {
-        "get": {
-          "id": "arvados.traits.get",
-          "path": "traits/{uuid}",
-          "httpMethod": "GET",
-          "description": "Gets a Trait's metadata by UUID.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the Trait in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "parameterOrder": [
-            "uuid"
-          ],
-          "response": {
-            "$ref": "Trait"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "index": {
-          "id": "arvados.traits.list",
-          "path": "traits",
-          "httpMethod": "GET",
-          "description": "List Traits.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Traits. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#traitList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "TraitList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "create": {
-          "id": "arvados.traits.create",
-          "path": "traits",
-          "httpMethod": "POST",
-          "description": "Create a new Trait.",
-          "parameters": {
-            "select": {
-              "type": "array",
-              "description": "Attributes of the new object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "ensure_unique_name": {
-              "type": "boolean",
-              "description": "Adjust name to ensure uniqueness instead of returning an error on (owner_uuid, name) collision.",
-              "location": "query",
-              "required": false,
-              "default": "false"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "Create object on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "trait": {
-                "$ref": "Trait"
-              }
-            }
-          },
-          "response": {
-            "$ref": "Trait"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "update": {
-          "id": "arvados.traits.update",
-          "path": "traits/{uuid}",
-          "httpMethod": "PUT",
-          "description": "Update attributes of an existing Trait.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the Trait in question.",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the updated object to return in the response.",
-              "required": false,
-              "location": "query"
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "trait": {
-                "$ref": "Trait"
-              }
-            }
-          },
-          "response": {
-            "$ref": "Trait"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "delete": {
-          "id": "arvados.traits.delete",
-          "path": "traits/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "Delete an existing Trait.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the Trait in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "Trait"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "list": {
-          "id": "arvados.traits.list",
-          "path": "traits",
-          "httpMethod": "GET",
-          "description": "List Traits.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Traits. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#traitList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "TraitList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "show": {
-          "id": "arvados.traits.show",
-          "path": "traits/{uuid}",
-          "httpMethod": "GET",
-          "description": "show traits",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the object to return in the response.",
-              "required": false,
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "Trait"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "destroy": {
-          "id": "arvados.traits.destroy",
-          "path": "traits/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "destroy traits",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "Trait"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        }
-      }
-    },
-    "users": {
-      "methods": {
-        "get": {
-          "id": "arvados.users.get",
-          "path": "users/{uuid}",
-          "httpMethod": "GET",
-          "description": "Gets a User's metadata by UUID.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the User in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "parameterOrder": [
-            "uuid"
-          ],
-          "response": {
-            "$ref": "User"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "index": {
-          "id": "arvados.users.list",
-          "path": "users",
-          "httpMethod": "GET",
-          "description": "List Users.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Users. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#userList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "UserList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "create": {
-          "id": "arvados.users.create",
-          "path": "users",
-          "httpMethod": "POST",
-          "description": "Create a new User.",
-          "parameters": {
-            "select": {
-              "type": "array",
-              "description": "Attributes of the new object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "ensure_unique_name": {
-              "type": "boolean",
-              "description": "Adjust name to ensure uniqueness instead of returning an error on (owner_uuid, name) collision.",
-              "location": "query",
-              "required": false,
-              "default": "false"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "Create object on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "user": {
-                "$ref": "User"
-              }
-            }
-          },
-          "response": {
-            "$ref": "User"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "update": {
-          "id": "arvados.users.update",
-          "path": "users/{uuid}",
-          "httpMethod": "PUT",
-          "description": "Update attributes of an existing User.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the User in question.",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the updated object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "user": {
-                "$ref": "User"
-              }
-            }
-          },
-          "response": {
-            "$ref": "User"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "delete": {
-          "id": "arvados.users.delete",
-          "path": "users/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "Delete an existing User.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the User in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "User"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "current": {
-          "id": "arvados.users.current",
-          "path": "users/current",
-          "httpMethod": "GET",
-          "description": "current users",
-          "parameters": {},
-          "response": {
-            "$ref": "User"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "system": {
-          "id": "arvados.users.system",
-          "path": "users/system",
-          "httpMethod": "GET",
-          "description": "system users",
-          "parameters": {},
-          "response": {
-            "$ref": "User"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "activate": {
-          "id": "arvados.users.activate",
-          "path": "users/{uuid}/activate",
-          "httpMethod": "POST",
-          "description": "activate users",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "User"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "setup": {
-          "id": "arvados.users.setup",
-          "path": "users/setup",
-          "httpMethod": "POST",
-          "description": "setup users",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "user": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "repo_name": {
-              "type": "string",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "vm_uuid": {
-              "type": "string",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "send_notification_email": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "User"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "unsetup": {
-          "id": "arvados.users.unsetup",
-          "path": "users/{uuid}/unsetup",
-          "httpMethod": "POST",
-          "description": "unsetup users",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "User"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "merge": {
-          "id": "arvados.users.merge",
-          "path": "users/merge",
-          "httpMethod": "POST",
-          "description": "merge users",
-          "parameters": {
-            "new_owner_uuid": {
-              "type": "string",
-              "required": true,
-              "description": "",
-              "location": "query"
-            },
-            "new_user_token": {
-              "type": "string",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "redirect_to_new_user": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "old_user_uuid": {
-              "type": "string",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "new_user_uuid": {
-              "type": "string",
-              "required": false,
-              "description": "",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "User"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "list": {
-          "id": "arvados.users.list",
-          "path": "users",
-          "httpMethod": "GET",
-          "description": "List Users.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Users. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#userList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "UserList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "show": {
-          "id": "arvados.users.show",
-          "path": "users/{uuid}",
-          "httpMethod": "GET",
-          "description": "show users",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the object to return in the response.",
-              "required": false,
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "User"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "destroy": {
-          "id": "arvados.users.destroy",
-          "path": "users/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "destroy users",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "User"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        }
-      }
-    },
-    "user_agreements": {
-      "methods": {
-        "get": {
-          "id": "arvados.user_agreements.get",
-          "path": "user_agreements/{uuid}",
-          "httpMethod": "GET",
-          "description": "Gets a UserAgreement's metadata by UUID.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the UserAgreement in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "parameterOrder": [
-            "uuid"
-          ],
-          "response": {
-            "$ref": "UserAgreement"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "index": {
-          "id": "arvados.user_agreements.list",
-          "path": "user_agreements",
-          "httpMethod": "GET",
-          "description": "List UserAgreements.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching UserAgreements. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#userAgreementList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "UserAgreementList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "create": {
-          "id": "arvados.user_agreements.create",
-          "path": "user_agreements",
-          "httpMethod": "POST",
-          "description": "Create a new UserAgreement.",
-          "parameters": {
-            "select": {
-              "type": "array",
-              "description": "Attributes of the new object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "ensure_unique_name": {
-              "type": "boolean",
-              "description": "Adjust name to ensure uniqueness instead of returning an error on (owner_uuid, name) collision.",
-              "location": "query",
-              "required": false,
-              "default": "false"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "Create object on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "user_agreement": {
-                "$ref": "UserAgreement"
-              }
-            }
-          },
-          "response": {
-            "$ref": "UserAgreement"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "update": {
-          "id": "arvados.user_agreements.update",
-          "path": "user_agreements/{uuid}",
-          "httpMethod": "PUT",
-          "description": "Update attributes of an existing UserAgreement.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the UserAgreement in question.",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the updated object to return in the response.",
-              "required": false,
-              "location": "query"
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "user_agreement": {
-                "$ref": "UserAgreement"
-              }
-            }
-          },
-          "response": {
-            "$ref": "UserAgreement"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "delete": {
-          "id": "arvados.user_agreements.delete",
-          "path": "user_agreements/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "Delete an existing UserAgreement.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the UserAgreement in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "UserAgreement"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "signatures": {
-          "id": "arvados.user_agreements.signatures",
-          "path": "user_agreements/signatures",
-          "httpMethod": "GET",
-          "description": "signatures user_agreements",
-          "parameters": {},
-          "response": {
-            "$ref": "UserAgreement"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "sign": {
-          "id": "arvados.user_agreements.sign",
-          "path": "user_agreements/sign",
-          "httpMethod": "POST",
-          "description": "sign user_agreements",
-          "parameters": {},
-          "response": {
-            "$ref": "UserAgreement"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "list": {
-          "id": "arvados.user_agreements.list",
-          "path": "user_agreements",
-          "httpMethod": "GET",
-          "description": "List UserAgreements.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching UserAgreements. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#userAgreementList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "UserAgreementList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "new": {
-          "id": "arvados.user_agreements.new",
-          "path": "user_agreements/new",
-          "httpMethod": "GET",
-          "description": "new user_agreements",
-          "parameters": {},
-          "response": {
-            "$ref": "UserAgreement"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "show": {
-          "id": "arvados.user_agreements.show",
-          "path": "user_agreements/{uuid}",
-          "httpMethod": "GET",
-          "description": "show user_agreements",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the object to return in the response.",
-              "required": false,
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "UserAgreement"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "destroy": {
-          "id": "arvados.user_agreements.destroy",
-          "path": "user_agreements/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "destroy user_agreements",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "UserAgreement"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        }
-      }
-    },
-    "virtual_machines": {
-      "methods": {
-        "get": {
-          "id": "arvados.virtual_machines.get",
-          "path": "virtual_machines/{uuid}",
-          "httpMethod": "GET",
-          "description": "Gets a VirtualMachine's metadata by UUID.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the VirtualMachine in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "parameterOrder": [
-            "uuid"
-          ],
-          "response": {
-            "$ref": "VirtualMachine"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "index": {
-          "id": "arvados.virtual_machines.list",
-          "path": "virtual_machines",
-          "httpMethod": "GET",
-          "description": "List VirtualMachines.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching VirtualMachines. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#virtualMachineList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "VirtualMachineList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "create": {
-          "id": "arvados.virtual_machines.create",
-          "path": "virtual_machines",
-          "httpMethod": "POST",
-          "description": "Create a new VirtualMachine.",
-          "parameters": {
-            "select": {
-              "type": "array",
-              "description": "Attributes of the new object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "ensure_unique_name": {
-              "type": "boolean",
-              "description": "Adjust name to ensure uniqueness instead of returning an error on (owner_uuid, name) collision.",
-              "location": "query",
-              "required": false,
-              "default": "false"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "Create object on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "virtual_machine": {
-                "$ref": "VirtualMachine"
-              }
-            }
-          },
-          "response": {
-            "$ref": "VirtualMachine"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "update": {
-          "id": "arvados.virtual_machines.update",
-          "path": "virtual_machines/{uuid}",
-          "httpMethod": "PUT",
-          "description": "Update attributes of an existing VirtualMachine.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the VirtualMachine in question.",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the updated object to return in the response.",
-              "required": false,
-              "location": "query"
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "virtual_machine": {
-                "$ref": "VirtualMachine"
-              }
-            }
-          },
-          "response": {
-            "$ref": "VirtualMachine"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "delete": {
-          "id": "arvados.virtual_machines.delete",
-          "path": "virtual_machines/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "Delete an existing VirtualMachine.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the VirtualMachine in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "VirtualMachine"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "logins": {
-          "id": "arvados.virtual_machines.logins",
-          "path": "virtual_machines/{uuid}/logins",
-          "httpMethod": "GET",
-          "description": "logins virtual_machines",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "VirtualMachine"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "get_all_logins": {
-          "id": "arvados.virtual_machines.get_all_logins",
-          "path": "virtual_machines/get_all_logins",
-          "httpMethod": "GET",
-          "description": "get_all_logins virtual_machines",
-          "parameters": {},
-          "response": {
-            "$ref": "VirtualMachine"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "list": {
-          "id": "arvados.virtual_machines.list",
-          "path": "virtual_machines",
-          "httpMethod": "GET",
-          "description": "List VirtualMachines.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching VirtualMachines. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#virtualMachineList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "VirtualMachineList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "show": {
-          "id": "arvados.virtual_machines.show",
-          "path": "virtual_machines/{uuid}",
-          "httpMethod": "GET",
-          "description": "show virtual_machines",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the object to return in the response.",
-              "required": false,
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "VirtualMachine"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "destroy": {
-          "id": "arvados.virtual_machines.destroy",
-          "path": "virtual_machines/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "destroy virtual_machines",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "VirtualMachine"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        }
-      }
-    },
-    "workflows": {
-      "methods": {
-        "get": {
-          "id": "arvados.workflows.get",
-          "path": "workflows/{uuid}",
-          "httpMethod": "GET",
-          "description": "Gets a Workflow's metadata by UUID.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the Workflow in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "parameterOrder": [
-            "uuid"
-          ],
-          "response": {
-            "$ref": "Workflow"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "index": {
-          "id": "arvados.workflows.list",
-          "path": "workflows",
-          "httpMethod": "GET",
-          "description": "List Workflows.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Workflows. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#workflowList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "WorkflowList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "create": {
-          "id": "arvados.workflows.create",
-          "path": "workflows",
-          "httpMethod": "POST",
-          "description": "Create a new Workflow.",
-          "parameters": {
-            "select": {
-              "type": "array",
-              "description": "Attributes of the new object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "ensure_unique_name": {
-              "type": "boolean",
-              "description": "Adjust name to ensure uniqueness instead of returning an error on (owner_uuid, name) collision.",
-              "location": "query",
-              "required": false,
-              "default": "false"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "Create object on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "workflow": {
-                "$ref": "Workflow"
-              }
-            }
-          },
-          "response": {
-            "$ref": "Workflow"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "update": {
-          "id": "arvados.workflows.update",
-          "path": "workflows/{uuid}",
-          "httpMethod": "PUT",
-          "description": "Update attributes of an existing Workflow.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the Workflow in question.",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the updated object to return in the response.",
-              "required": false,
-              "location": "query"
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "workflow": {
-                "$ref": "Workflow"
-              }
-            }
-          },
-          "response": {
-            "$ref": "Workflow"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "delete": {
-          "id": "arvados.workflows.delete",
-          "path": "workflows/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "Delete an existing Workflow.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the Workflow in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "Workflow"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "list": {
-          "id": "arvados.workflows.list",
-          "path": "workflows",
-          "httpMethod": "GET",
-          "description": "List Workflows.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Workflows. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#workflowList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "WorkflowList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "show": {
-          "id": "arvados.workflows.show",
-          "path": "workflows/{uuid}",
-          "httpMethod": "GET",
-          "description": "show workflows",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the object to return in the response.",
-              "required": false,
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "Workflow"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "destroy": {
-          "id": "arvados.workflows.destroy",
-          "path": "workflows/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "destroy workflows",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "Workflow"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        }
-      }
-    },
-    "configs": {
-      "methods": {
-        "get": {
-          "id": "arvados.configs.get",
-          "path": "config",
-          "httpMethod": "GET",
-          "description": "Get public config",
-          "parameters": {},
-          "parameterOrder": [],
-          "response": {},
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        }
-      }
-    },
-    "vocabularies": {
-      "methods": {
-        "get": {
-          "id": "arvados.vocabularies.get",
-          "path": "vocabulary",
-          "httpMethod": "GET",
-          "description": "Get vocabulary definition",
-          "parameters": {},
-          "parameterOrder": [],
-          "response": {},
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        }
-      }
-    },
-    "sys": {
-      "methods": {
-        "get": {
-          "id": "arvados.sys.trash_sweep",
-          "path": "sys/trash_sweep",
-          "httpMethod": "POST",
-          "description": "apply scheduled trash and delete operations",
-          "parameters": {},
-          "parameterOrder": [],
-          "response": {},
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        }
-      }
-    }
-  },
-  "revision": "20231117",
-  "schemas": {
-    "ApiClientList": {
-      "id": "ApiClientList",
-      "description": "ApiClient list",
-      "type": "object",
-      "properties": {
-        "kind": {
-          "type": "string",
-          "description": "Object type. Always arvados#apiClientList.",
-          "default": "arvados#apiClientList"
-        },
-        "etag": {
-          "type": "string",
-          "description": "List version."
-        },
-        "items": {
-          "type": "array",
-          "description": "The list of ApiClients.",
-          "items": {
-            "$ref": "ApiClient"
-          }
-        },
-        "next_link": {
-          "type": "string",
-          "description": "A link to the next page of ApiClients."
-        },
-        "next_page_token": {
-          "type": "string",
-          "description": "The page token for the next page of ApiClients."
-        },
-        "selfLink": {
-          "type": "string",
-          "description": "A link back to this list."
-        }
-      }
-    },
-    "ApiClient": {
-      "id": "ApiClient",
-      "description": "ApiClient",
-      "type": "object",
-      "uuidPrefix": "ozdt8",
-      "properties": {
-        "uuid": {
-          "type": "string"
-        },
-        "etag": {
-          "type": "string",
-          "description": "Object version."
-        },
-        "owner_uuid": {
-          "type": "string"
-        },
-        "modified_by_client_uuid": {
-          "type": "string"
-        },
-        "modified_by_user_uuid": {
-          "type": "string"
-        },
-        "modified_at": {
-          "type": "datetime"
-        },
-        "name": {
-          "type": "string"
-        },
-        "url_prefix": {
-          "type": "string"
-        },
-        "created_at": {
-          "type": "datetime"
-        },
-        "is_trusted": {
-          "type": "boolean"
-        }
-      }
-    },
-    "ApiClientAuthorizationList": {
-      "id": "ApiClientAuthorizationList",
-      "description": "ApiClientAuthorization list",
-      "type": "object",
-      "properties": {
-        "kind": {
-          "type": "string",
-          "description": "Object type. Always arvados#apiClientAuthorizationList.",
-          "default": "arvados#apiClientAuthorizationList"
-        },
-        "etag": {
-          "type": "string",
-          "description": "List version."
-        },
-        "items": {
-          "type": "array",
-          "description": "The list of ApiClientAuthorizations.",
-          "items": {
-            "$ref": "ApiClientAuthorization"
-          }
-        },
-        "next_link": {
-          "type": "string",
-          "description": "A link to the next page of ApiClientAuthorizations."
-        },
-        "next_page_token": {
-          "type": "string",
-          "description": "The page token for the next page of ApiClientAuthorizations."
-        },
-        "selfLink": {
-          "type": "string",
-          "description": "A link back to this list."
-        }
-      }
-    },
-    "ApiClientAuthorization": {
-      "id": "ApiClientAuthorization",
-      "description": "ApiClientAuthorization",
-      "type": "object",
-      "uuidPrefix": "gj3su",
-      "properties": {
-        "uuid": {
-          "type": "string"
-        },
-        "etag": {
-          "type": "string",
-          "description": "Object version."
-        },
-        "api_token": {
-          "type": "string"
-        },
-        "api_client_id": {
-          "type": "integer"
-        },
-        "user_id": {
-          "type": "integer"
-        },
-        "created_by_ip_address": {
-          "type": "string"
-        },
-        "last_used_by_ip_address": {
-          "type": "string"
-        },
-        "last_used_at": {
-          "type": "datetime"
-        },
-        "expires_at": {
-          "type": "datetime"
-        },
-        "created_at": {
-          "type": "datetime"
-        },
-        "default_owner_uuid": {
-          "type": "string"
-        },
-        "scopes": {
-          "type": "Array"
-        }
-      }
-    },
-    "AuthorizedKeyList": {
-      "id": "AuthorizedKeyList",
-      "description": "AuthorizedKey list",
-      "type": "object",
-      "properties": {
-        "kind": {
-          "type": "string",
-          "description": "Object type. Always arvados#authorizedKeyList.",
-          "default": "arvados#authorizedKeyList"
-        },
-        "etag": {
-          "type": "string",
-          "description": "List version."
-        },
-        "items": {
-          "type": "array",
-          "description": "The list of AuthorizedKeys.",
-          "items": {
-            "$ref": "AuthorizedKey"
-          }
-        },
-        "next_link": {
-          "type": "string",
-          "description": "A link to the next page of AuthorizedKeys."
-        },
-        "next_page_token": {
-          "type": "string",
-          "description": "The page token for the next page of AuthorizedKeys."
-        },
-        "selfLink": {
-          "type": "string",
-          "description": "A link back to this list."
-        }
-      }
-    },
-    "AuthorizedKey": {
-      "id": "AuthorizedKey",
-      "description": "AuthorizedKey",
-      "type": "object",
-      "uuidPrefix": "fngyi",
-      "properties": {
-        "uuid": {
-          "type": "string"
-        },
-        "etag": {
-          "type": "string",
-          "description": "Object version."
-        },
-        "owner_uuid": {
-          "type": "string"
-        },
-        "modified_by_client_uuid": {
-          "type": "string"
-        },
-        "modified_by_user_uuid": {
-          "type": "string"
-        },
-        "modified_at": {
-          "type": "datetime"
-        },
-        "name": {
-          "type": "string"
-        },
-        "key_type": {
-          "type": "string"
-        },
-        "authorized_user_uuid": {
-          "type": "string"
-        },
-        "public_key": {
-          "type": "text"
-        },
-        "expires_at": {
-          "type": "datetime"
-        },
-        "created_at": {
-          "type": "datetime"
-        }
-      }
-    },
-    "CollectionList": {
-      "id": "CollectionList",
-      "description": "Collection list",
-      "type": "object",
-      "properties": {
-        "kind": {
-          "type": "string",
-          "description": "Object type. Always arvados#collectionList.",
-          "default": "arvados#collectionList"
-        },
-        "etag": {
-          "type": "string",
-          "description": "List version."
-        },
-        "items": {
-          "type": "array",
-          "description": "The list of Collections.",
-          "items": {
-            "$ref": "Collection"
-          }
-        },
-        "next_link": {
-          "type": "string",
-          "description": "A link to the next page of Collections."
-        },
-        "next_page_token": {
-          "type": "string",
-          "description": "The page token for the next page of Collections."
-        },
-        "selfLink": {
-          "type": "string",
-          "description": "A link back to this list."
-        }
-      }
-    },
-    "Collection": {
-      "id": "Collection",
-      "description": "Collection",
-      "type": "object",
-      "uuidPrefix": "4zz18",
-      "properties": {
-        "uuid": {
-          "type": "string"
-        },
-        "etag": {
-          "type": "string",
-          "description": "Object version."
-        },
-        "owner_uuid": {
-          "type": "string"
-        },
-        "created_at": {
-          "type": "datetime"
-        },
-        "modified_by_client_uuid": {
-          "type": "string"
-        },
-        "modified_by_user_uuid": {
-          "type": "string"
-        },
-        "modified_at": {
-          "type": "datetime"
-        },
-        "portable_data_hash": {
-          "type": "string"
-        },
-        "replication_desired": {
-          "type": "integer"
-        },
-        "replication_confirmed_at": {
-          "type": "datetime"
-        },
-        "replication_confirmed": {
-          "type": "integer"
-        },
-        "manifest_text": {
-          "type": "text"
-        },
-        "name": {
-          "type": "string"
-        },
-        "description": {
-          "type": "string"
-        },
-        "properties": {
-          "type": "Hash"
-        },
-        "delete_at": {
-          "type": "datetime"
-        },
-        "trash_at": {
-          "type": "datetime"
-        },
-        "is_trashed": {
-          "type": "boolean"
-        },
-        "storage_classes_desired": {
-          "type": "Array"
-        },
-        "storage_classes_confirmed": {
-          "type": "Array"
-        },
-        "storage_classes_confirmed_at": {
-          "type": "datetime"
-        },
-        "current_version_uuid": {
-          "type": "string"
-        },
-        "version": {
-          "type": "integer"
-        },
-        "preserve_version": {
-          "type": "boolean"
-        },
-        "file_count": {
-          "type": "integer"
-        },
-        "file_size_total": {
-          "type": "integer"
-        }
-      }
-    },
-    "ContainerList": {
-      "id": "ContainerList",
-      "description": "Container list",
-      "type": "object",
-      "properties": {
-        "kind": {
-          "type": "string",
-          "description": "Object type. Always arvados#containerList.",
-          "default": "arvados#containerList"
-        },
-        "etag": {
-          "type": "string",
-          "description": "List version."
-        },
-        "items": {
-          "type": "array",
-          "description": "The list of Containers.",
-          "items": {
-            "$ref": "Container"
-          }
-        },
-        "next_link": {
-          "type": "string",
-          "description": "A link to the next page of Containers."
-        },
-        "next_page_token": {
-          "type": "string",
-          "description": "The page token for the next page of Containers."
-        },
-        "selfLink": {
-          "type": "string",
-          "description": "A link back to this list."
-        }
-      }
-    },
-    "Container": {
-      "id": "Container",
-      "description": "Container",
-      "type": "object",
-      "uuidPrefix": "dz642",
-      "properties": {
-        "uuid": {
-          "type": "string"
-        },
-        "etag": {
-          "type": "string",
-          "description": "Object version."
-        },
-        "owner_uuid": {
-          "type": "string"
-        },
-        "created_at": {
-          "type": "datetime"
-        },
-        "modified_at": {
-          "type": "datetime"
-        },
-        "modified_by_client_uuid": {
-          "type": "string"
-        },
-        "modified_by_user_uuid": {
-          "type": "string"
-        },
-        "state": {
-          "type": "string"
-        },
-        "started_at": {
-          "type": "datetime"
-        },
-        "finished_at": {
-          "type": "datetime"
-        },
-        "log": {
-          "type": "string"
-        },
-        "environment": {
-          "type": "Hash"
-        },
-        "cwd": {
-          "type": "string"
-        },
-        "command": {
-          "type": "Array"
-        },
-        "output_path": {
-          "type": "string"
-        },
-        "mounts": {
-          "type": "Hash"
-        },
-        "runtime_constraints": {
-          "type": "Hash"
-        },
-        "output": {
-          "type": "string"
-        },
-        "container_image": {
-          "type": "string"
-        },
-        "progress": {
-          "type": "float"
-        },
-        "priority": {
-          "type": "integer"
-        },
-        "exit_code": {
-          "type": "integer"
-        },
-        "auth_uuid": {
-          "type": "string"
-        },
-        "locked_by_uuid": {
-          "type": "string"
-        },
-        "scheduling_parameters": {
-          "type": "Hash"
-        },
-        "runtime_status": {
-          "type": "Hash"
-        },
-        "runtime_user_uuid": {
-          "type": "text"
-        },
-        "runtime_auth_scopes": {
-          "type": "Array"
-        },
-        "lock_count": {
-          "type": "integer"
-        },
-        "gateway_address": {
-          "type": "string"
-        },
-        "interactive_session_started": {
-          "type": "boolean"
-        },
-        "output_storage_classes": {
-          "type": "Array"
-        },
-        "output_properties": {
-          "type": "Hash"
-        },
-        "cost": {
-          "type": "float"
-        },
-        "subrequests_cost": {
-          "type": "float"
-        }
-      }
-    },
-    "ContainerRequestList": {
-      "id": "ContainerRequestList",
-      "description": "ContainerRequest list",
-      "type": "object",
-      "properties": {
-        "kind": {
-          "type": "string",
-          "description": "Object type. Always arvados#containerRequestList.",
-          "default": "arvados#containerRequestList"
-        },
-        "etag": {
-          "type": "string",
-          "description": "List version."
-        },
-        "items": {
-          "type": "array",
-          "description": "The list of ContainerRequests.",
-          "items": {
-            "$ref": "ContainerRequest"
-          }
-        },
-        "next_link": {
-          "type": "string",
-          "description": "A link to the next page of ContainerRequests."
-        },
-        "next_page_token": {
-          "type": "string",
-          "description": "The page token for the next page of ContainerRequests."
-        },
-        "selfLink": {
-          "type": "string",
-          "description": "A link back to this list."
-        }
-      }
-    },
-    "ContainerRequest": {
-      "id": "ContainerRequest",
-      "description": "ContainerRequest",
-      "type": "object",
-      "uuidPrefix": "xvhdp",
-      "properties": {
-        "uuid": {
-          "type": "string"
-        },
-        "etag": {
-          "type": "string",
-          "description": "Object version."
-        },
-        "owner_uuid": {
-          "type": "string"
-        },
-        "created_at": {
-          "type": "datetime"
-        },
-        "modified_at": {
-          "type": "datetime"
-        },
-        "modified_by_client_uuid": {
-          "type": "string"
-        },
-        "modified_by_user_uuid": {
-          "type": "string"
-        },
-        "name": {
-          "type": "string"
-        },
-        "description": {
-          "type": "text"
-        },
-        "properties": {
-          "type": "Hash"
-        },
-        "state": {
-          "type": "string"
-        },
-        "requesting_container_uuid": {
-          "type": "string"
-        },
-        "container_uuid": {
-          "type": "string"
-        },
-        "container_count_max": {
-          "type": "integer"
-        },
-        "mounts": {
-          "type": "Hash"
-        },
-        "runtime_constraints": {
-          "type": "Hash"
-        },
-        "container_image": {
-          "type": "string"
-        },
-        "environment": {
-          "type": "Hash"
-        },
-        "cwd": {
-          "type": "string"
-        },
-        "command": {
-          "type": "Array"
-        },
-        "output_path": {
-          "type": "string"
-        },
-        "priority": {
-          "type": "integer"
-        },
-        "expires_at": {
-          "type": "datetime"
-        },
-        "filters": {
-          "type": "text"
-        },
-        "container_count": {
-          "type": "integer"
-        },
-        "use_existing": {
-          "type": "boolean"
-        },
-        "scheduling_parameters": {
-          "type": "Hash"
-        },
-        "output_uuid": {
-          "type": "string"
-        },
-        "log_uuid": {
-          "type": "string"
-        },
-        "output_name": {
-          "type": "string"
-        },
-        "output_ttl": {
-          "type": "integer"
-        },
-        "output_storage_classes": {
-          "type": "Array"
-        },
-        "output_properties": {
-          "type": "Hash"
-        },
-        "cumulative_cost": {
-          "type": "float"
-        }
-      }
-    },
-    "GroupList": {
-      "id": "GroupList",
-      "description": "Group list",
-      "type": "object",
-      "properties": {
-        "kind": {
-          "type": "string",
-          "description": "Object type. Always arvados#groupList.",
-          "default": "arvados#groupList"
-        },
-        "etag": {
-          "type": "string",
-          "description": "List version."
-        },
-        "items": {
-          "type": "array",
-          "description": "The list of Groups.",
-          "items": {
-            "$ref": "Group"
-          }
-        },
-        "next_link": {
-          "type": "string",
-          "description": "A link to the next page of Groups."
-        },
-        "next_page_token": {
-          "type": "string",
-          "description": "The page token for the next page of Groups."
-        },
-        "selfLink": {
-          "type": "string",
-          "description": "A link back to this list."
-        }
-      }
-    },
-    "Group": {
-      "id": "Group",
-      "description": "Group",
-      "type": "object",
-      "uuidPrefix": "j7d0g",
-      "properties": {
-        "uuid": {
-          "type": "string"
-        },
-        "etag": {
-          "type": "string",
-          "description": "Object version."
-        },
-        "owner_uuid": {
-          "type": "string"
-        },
-        "created_at": {
-          "type": "datetime"
-        },
-        "modified_by_client_uuid": {
-          "type": "string"
-        },
-        "modified_by_user_uuid": {
-          "type": "string"
-        },
-        "modified_at": {
-          "type": "datetime"
-        },
-        "name": {
-          "type": "string"
-        },
-        "description": {
-          "type": "string"
-        },
-        "group_class": {
-          "type": "string"
-        },
-        "trash_at": {
-          "type": "datetime"
-        },
-        "is_trashed": {
-          "type": "boolean"
-        },
-        "delete_at": {
-          "type": "datetime"
-        },
-        "properties": {
-          "type": "Hash"
-        },
-        "frozen_by_uuid": {
-          "type": "string"
+          "id": "arvados.workflows.destroy",
+          "path": "workflows/{uuid}",
+          "httpMethod": "DELETE",
+          "description": "destroy workflows",
+          "parameters": {
+            "uuid": {
+              "type": "string",
+              "description": "",
+              "required": true,
+              "location": "path"
+            }
+          },
+          "response": {
+            "$ref": "Workflow"
+          },
+          "scopes": [
+            "https://api.arvados.org/auth/arvados"
+          ]
         }
       }
     },
-    "HumanList": {
-      "id": "HumanList",
-      "description": "Human list",
-      "type": "object",
-      "properties": {
-        "kind": {
-          "type": "string",
-          "description": "Object type. Always arvados#humanList.",
-          "default": "arvados#humanList"
-        },
-        "etag": {
-          "type": "string",
-          "description": "List version."
-        },
-        "items": {
-          "type": "array",
-          "description": "The list of Humans.",
-          "items": {
-            "$ref": "Human"
-          }
-        },
-        "next_link": {
-          "type": "string",
-          "description": "A link to the next page of Humans."
-        },
-        "next_page_token": {
-          "type": "string",
-          "description": "The page token for the next page of Humans."
-        },
-        "selfLink": {
-          "type": "string",
-          "description": "A link back to this list."
+    "configs": {
+      "methods": {
+        "get": {
+          "id": "arvados.configs.get",
+          "path": "config",
+          "httpMethod": "GET",
+          "description": "Get public config",
+          "parameters": {},
+          "parameterOrder": [],
+          "response": {},
+          "scopes": [
+            "https://api.arvados.org/auth/arvados",
+            "https://api.arvados.org/auth/arvados.readonly"
+          ]
         }
       }
     },
-    "Human": {
-      "id": "Human",
-      "description": "Human",
-      "type": "object",
-      "uuidPrefix": "7a9it",
-      "properties": {
-        "uuid": {
-          "type": "string"
-        },
-        "etag": {
-          "type": "string",
-          "description": "Object version."
-        },
-        "owner_uuid": {
-          "type": "string"
-        },
-        "modified_by_client_uuid": {
-          "type": "string"
-        },
-        "modified_by_user_uuid": {
-          "type": "string"
-        },
-        "modified_at": {
-          "type": "datetime"
-        },
-        "properties": {
-          "type": "Hash"
-        },
-        "created_at": {
-          "type": "datetime"
+    "vocabularies": {
+      "methods": {
+        "get": {
+          "id": "arvados.vocabularies.get",
+          "path": "vocabulary",
+          "httpMethod": "GET",
+          "description": "Get vocabulary definition",
+          "parameters": {},
+          "parameterOrder": [],
+          "response": {},
+          "scopes": [
+            "https://api.arvados.org/auth/arvados",
+            "https://api.arvados.org/auth/arvados.readonly"
+          ]
         }
       }
     },
-    "JobList": {
-      "id": "JobList",
-      "description": "Job list",
+    "sys": {
+      "methods": {
+        "get": {
+          "id": "arvados.sys.trash_sweep",
+          "path": "sys/trash_sweep",
+          "httpMethod": "POST",
+          "description": "apply scheduled trash and delete operations",
+          "parameters": {},
+          "parameterOrder": [],
+          "response": {},
+          "scopes": [
+            "https://api.arvados.org/auth/arvados",
+            "https://api.arvados.org/auth/arvados.readonly"
+          ]
+        }
+      }
+    }
+  },
+  "revision": "20231117",
+  "schemas": {
+    "ApiClientList": {
+      "id": "ApiClientList",
+      "description": "ApiClient list",
       "type": "object",
       "properties": {
         "kind": {
           "type": "string",
-          "description": "Object type. Always arvados#jobList.",
-          "default": "arvados#jobList"
+          "description": "Object type. Always arvados#apiClientList.",
+          "default": "arvados#apiClientList"
         },
         "etag": {
           "type": "string",
@@ -9940,18 +5579,18 @@
         },
         "items": {
           "type": "array",
-          "description": "The list of Jobs.",
+          "description": "The list of ApiClients.",
           "items": {
-            "$ref": "Job"
+            "$ref": "ApiClient"
           }
         },
         "next_link": {
           "type": "string",
-          "description": "A link to the next page of Jobs."
+          "description": "A link to the next page of ApiClients."
         },
         "next_page_token": {
           "type": "string",
-          "description": "The page token for the next page of Jobs."
+          "description": "The page token for the next page of ApiClients."
         },
         "selfLink": {
           "type": "string",
@@ -9959,11 +5598,11 @@
         }
       }
     },
-    "Job": {
-      "id": "Job",
-      "description": "Job",
+    "ApiClient": {
+      "id": "ApiClient",
+      "description": "ApiClient",
       "type": "object",
-      "uuidPrefix": "8i9sb",
+      "uuidPrefix": "ozdt8",
       "properties": {
         "uuid": {
           "type": "string"
@@ -9984,95 +5623,29 @@
         "modified_at": {
           "type": "datetime"
         },
-        "submit_id": {
-          "type": "string"
-        },
-        "script": {
-          "type": "string"
-        },
-        "script_version": {
-          "type": "string"
-        },
-        "script_parameters": {
-          "type": "Hash"
-        },
-        "cancelled_by_client_uuid": {
-          "type": "string"
-        },
-        "cancelled_by_user_uuid": {
+        "name": {
           "type": "string"
         },
-        "cancelled_at": {
-          "type": "datetime"
-        },
-        "started_at": {
-          "type": "datetime"
-        },
-        "finished_at": {
-          "type": "datetime"
-        },
-        "running": {
-          "type": "boolean"
-        },
-        "success": {
-          "type": "boolean"
-        },
-        "output": {
+        "url_prefix": {
           "type": "string"
         },
         "created_at": {
           "type": "datetime"
         },
-        "is_locked_by_uuid": {
-          "type": "string"
-        },
-        "log": {
-          "type": "string"
-        },
-        "tasks_summary": {
-          "type": "Hash"
-        },
-        "runtime_constraints": {
-          "type": "Hash"
-        },
-        "nondeterministic": {
+        "is_trusted": {
           "type": "boolean"
-        },
-        "repository": {
-          "type": "string"
-        },
-        "supplied_script_version": {
-          "type": "string"
-        },
-        "docker_image_locator": {
-          "type": "string"
-        },
-        "priority": {
-          "type": "integer"
-        },
-        "description": {
-          "type": "string"
-        },
-        "state": {
-          "type": "string"
-        },
-        "arvados_sdk_version": {
-          "type": "string"
-        },
-        "components": {
-          "type": "Hash"
         }
       }
     },
-    "JobTaskList": {
-      "id": "JobTaskList",
-      "description": "JobTask list",
+    "ApiClientAuthorizationList": {
+      "id": "ApiClientAuthorizationList",
+      "description": "ApiClientAuthorization list",
       "type": "object",
       "properties": {
         "kind": {
           "type": "string",
-          "description": "Object type. Always arvados#jobTaskList.",
-          "default": "arvados#jobTaskList"
+          "description": "Object type. Always arvados#apiClientAuthorizationList.",
+          "default": "arvados#apiClientAuthorizationList"
         },
         "etag": {
           "type": "string",
@@ -10080,18 +5653,18 @@
         },
         "items": {
           "type": "array",
-          "description": "The list of JobTasks.",
+          "description": "The list of ApiClientAuthorizations.",
           "items": {
-            "$ref": "JobTask"
+            "$ref": "ApiClientAuthorization"
           }
         },
         "next_link": {
           "type": "string",
-          "description": "A link to the next page of JobTasks."
+          "description": "A link to the next page of ApiClientAuthorizations."
         },
         "next_page_token": {
           "type": "string",
-          "description": "The page token for the next page of JobTasks."
+          "description": "The page token for the next page of ApiClientAuthorizations."
         },
         "selfLink": {
           "type": "string",
@@ -10099,11 +5672,11 @@
         }
       }
     },
-    "JobTask": {
-      "id": "JobTask",
-      "description": "JobTask",
+    "ApiClientAuthorization": {
+      "id": "ApiClientAuthorization",
+      "description": "ApiClientAuthorization",
       "type": "object",
-      "uuidPrefix": "ot0gb",
+      "uuidPrefix": "gj3su",
       "properties": {
         "uuid": {
           "type": "string"
@@ -10112,62 +5685,47 @@
           "type": "string",
           "description": "Object version."
         },
-        "owner_uuid": {
-          "type": "string"
-        },
-        "modified_by_client_uuid": {
-          "type": "string"
-        },
-        "modified_by_user_uuid": {
+        "api_token": {
           "type": "string"
         },
-        "modified_at": {
-          "type": "datetime"
-        },
-        "job_uuid": {
-          "type": "string"
+        "api_client_id": {
+          "type": "integer"
         },
-        "sequence": {
+        "user_id": {
           "type": "integer"
         },
-        "parameters": {
-          "type": "Hash"
+        "created_by_ip_address": {
+          "type": "string"
         },
-        "output": {
-          "type": "text"
+        "last_used_by_ip_address": {
+          "type": "string"
         },
-        "progress": {
-          "type": "float"
+        "last_used_at": {
+          "type": "datetime"
         },
-        "success": {
-          "type": "boolean"
+        "expires_at": {
+          "type": "datetime"
         },
         "created_at": {
           "type": "datetime"
         },
-        "created_by_job_task_uuid": {
+        "default_owner_uuid": {
           "type": "string"
         },
-        "qsequence": {
-          "type": "integer"
-        },
-        "started_at": {
-          "type": "datetime"
-        },
-        "finished_at": {
-          "type": "datetime"
+        "scopes": {
+          "type": "Array"
         }
       }
     },
-    "KeepDiskList": {
-      "id": "KeepDiskList",
-      "description": "KeepDisk list",
+    "AuthorizedKeyList": {
+      "id": "AuthorizedKeyList",
+      "description": "AuthorizedKey list",
       "type": "object",
       "properties": {
         "kind": {
           "type": "string",
-          "description": "Object type. Always arvados#keepDiskList.",
-          "default": "arvados#keepDiskList"
+          "description": "Object type. Always arvados#authorizedKeyList.",
+          "default": "arvados#authorizedKeyList"
         },
         "etag": {
           "type": "string",
@@ -10175,18 +5733,18 @@
         },
         "items": {
           "type": "array",
-          "description": "The list of KeepDisks.",
+          "description": "The list of AuthorizedKeys.",
           "items": {
-            "$ref": "KeepDisk"
+            "$ref": "AuthorizedKey"
           }
         },
         "next_link": {
           "type": "string",
-          "description": "A link to the next page of KeepDisks."
+          "description": "A link to the next page of AuthorizedKeys."
         },
         "next_page_token": {
           "type": "string",
-          "description": "The page token for the next page of KeepDisks."
+          "description": "The page token for the next page of AuthorizedKeys."
         },
         "selfLink": {
           "type": "string",
@@ -10194,11 +5752,11 @@
         }
       }
     },
-    "KeepDisk": {
-      "id": "KeepDisk",
-      "description": "KeepDisk",
+    "AuthorizedKey": {
+      "id": "AuthorizedKey",
+      "description": "AuthorizedKey",
       "type": "object",
-      "uuidPrefix": "penuu",
+      "uuidPrefix": "fngyi",
       "properties": {
         "uuid": {
           "type": "string"
@@ -10219,50 +5777,35 @@
         "modified_at": {
           "type": "datetime"
         },
-        "node_uuid": {
+        "name": {
           "type": "string"
         },
-        "filesystem_uuid": {
+        "key_type": {
           "type": "string"
         },
-        "bytes_total": {
-          "type": "integer"
-        },
-        "bytes_free": {
-          "type": "integer"
-        },
-        "is_readable": {
-          "type": "boolean"
-        },
-        "is_writable": {
-          "type": "boolean"
-        },
-        "last_read_at": {
-          "type": "datetime"
+        "authorized_user_uuid": {
+          "type": "string"
         },
-        "last_write_at": {
-          "type": "datetime"
+        "public_key": {
+          "type": "text"
         },
-        "last_ping_at": {
+        "expires_at": {
           "type": "datetime"
         },
         "created_at": {
           "type": "datetime"
-        },
-        "keep_service_uuid": {
-          "type": "string"
         }
       }
     },
-    "KeepServiceList": {
-      "id": "KeepServiceList",
-      "description": "KeepService list",
+    "CollectionList": {
+      "id": "CollectionList",
+      "description": "Collection list",
       "type": "object",
       "properties": {
         "kind": {
           "type": "string",
-          "description": "Object type. Always arvados#keepServiceList.",
-          "default": "arvados#keepServiceList"
+          "description": "Object type. Always arvados#collectionList.",
+          "default": "arvados#collectionList"
         },
         "etag": {
           "type": "string",
@@ -10270,18 +5813,18 @@
         },
         "items": {
           "type": "array",
-          "description": "The list of KeepServices.",
+          "description": "The list of Collections.",
           "items": {
-            "$ref": "KeepService"
+            "$ref": "Collection"
           }
         },
         "next_link": {
           "type": "string",
-          "description": "A link to the next page of KeepServices."
+          "description": "A link to the next page of Collections."
         },
         "next_page_token": {
           "type": "string",
-          "description": "The page token for the next page of KeepServices."
+          "description": "The page token for the next page of Collections."
         },
         "selfLink": {
           "type": "string",
@@ -10289,11 +5832,11 @@
         }
       }
     },
-    "KeepService": {
-      "id": "KeepService",
-      "description": "KeepService",
+    "Collection": {
+      "id": "Collection",
+      "description": "Collection",
       "type": "object",
-      "uuidPrefix": "bi6l4",
+      "uuidPrefix": "4zz18",
       "properties": {
         "uuid": {
           "type": "string"
@@ -10305,6 +5848,9 @@
         "owner_uuid": {
           "type": "string"
         },
+        "created_at": {
+          "type": "datetime"
+        },
         "modified_by_client_uuid": {
           "type": "string"
         },
@@ -10314,35 +5860,74 @@
         "modified_at": {
           "type": "datetime"
         },
-        "service_host": {
+        "portable_data_hash": {
           "type": "string"
         },
-        "service_port": {
+        "replication_desired": {
           "type": "integer"
         },
-        "service_ssl_flag": {
-          "type": "boolean"
+        "replication_confirmed_at": {
+          "type": "datetime"
         },
-        "service_type": {
+        "replication_confirmed": {
+          "type": "integer"
+        },
+        "manifest_text": {
+          "type": "text"
+        },
+        "name": {
           "type": "string"
         },
-        "created_at": {
+        "description": {
+          "type": "string"
+        },
+        "properties": {
+          "type": "Hash"
+        },
+        "delete_at": {
           "type": "datetime"
         },
-        "read_only": {
+        "trash_at": {
+          "type": "datetime"
+        },
+        "is_trashed": {
+          "type": "boolean"
+        },
+        "storage_classes_desired": {
+          "type": "Array"
+        },
+        "storage_classes_confirmed": {
+          "type": "Array"
+        },
+        "storage_classes_confirmed_at": {
+          "type": "datetime"
+        },
+        "current_version_uuid": {
+          "type": "string"
+        },
+        "version": {
+          "type": "integer"
+        },
+        "preserve_version": {
           "type": "boolean"
+        },
+        "file_count": {
+          "type": "integer"
+        },
+        "file_size_total": {
+          "type": "integer"
         }
       }
     },
-    "LinkList": {
-      "id": "LinkList",
-      "description": "Link list",
+    "ContainerList": {
+      "id": "ContainerList",
+      "description": "Container list",
       "type": "object",
       "properties": {
         "kind": {
           "type": "string",
-          "description": "Object type. Always arvados#linkList.",
-          "default": "arvados#linkList"
+          "description": "Object type. Always arvados#containerList.",
+          "default": "arvados#containerList"
         },
         "etag": {
           "type": "string",
@@ -10350,18 +5935,18 @@
         },
         "items": {
           "type": "array",
-          "description": "The list of Links.",
+          "description": "The list of Containers.",
           "items": {
-            "$ref": "Link"
+            "$ref": "Container"
           }
         },
         "next_link": {
           "type": "string",
-          "description": "A link to the next page of Links."
+          "description": "A link to the next page of Containers."
         },
         "next_page_token": {
           "type": "string",
-          "description": "The page token for the next page of Links."
+          "description": "The page token for the next page of Containers."
         },
         "selfLink": {
           "type": "string",
@@ -10369,11 +5954,11 @@
         }
       }
     },
-    "Link": {
-      "id": "Link",
-      "description": "Link",
+    "Container": {
+      "id": "Container",
+      "description": "Container",
       "type": "object",
-      "uuidPrefix": "o0j2j",
+      "uuidPrefix": "dz642",
       "properties": {
         "uuid": {
           "type": "string"
@@ -10388,127 +5973,110 @@
         "created_at": {
           "type": "datetime"
         },
+        "modified_at": {
+          "type": "datetime"
+        },
         "modified_by_client_uuid": {
           "type": "string"
         },
         "modified_by_user_uuid": {
           "type": "string"
         },
-        "modified_at": {
+        "state": {
+          "type": "string"
+        },
+        "started_at": {
           "type": "datetime"
         },
-        "tail_uuid": {
-          "type": "string"
+        "finished_at": {
+          "type": "datetime"
         },
-        "link_class": {
+        "log": {
           "type": "string"
         },
-        "name": {
-          "type": "string"
+        "environment": {
+          "type": "Hash"
         },
-        "head_uuid": {
+        "cwd": {
           "type": "string"
         },
-        "properties": {
-          "type": "Hash"
-        }
-      }
-    },
-    "LogList": {
-      "id": "LogList",
-      "description": "Log list",
-      "type": "object",
-      "properties": {
-        "kind": {
-          "type": "string",
-          "description": "Object type. Always arvados#logList.",
-          "default": "arvados#logList"
+        "command": {
+          "type": "Array"
         },
-        "etag": {
-          "type": "string",
-          "description": "List version."
+        "output_path": {
+          "type": "string"
         },
-        "items": {
-          "type": "array",
-          "description": "The list of Logs.",
-          "items": {
-            "$ref": "Log"
-          }
+        "mounts": {
+          "type": "Hash"
         },
-        "next_link": {
-          "type": "string",
-          "description": "A link to the next page of Logs."
+        "runtime_constraints": {
+          "type": "Hash"
         },
-        "next_page_token": {
-          "type": "string",
-          "description": "The page token for the next page of Logs."
+        "output": {
+          "type": "string"
         },
-        "selfLink": {
-          "type": "string",
-          "description": "A link back to this list."
-        }
-      }
-    },
-    "Log": {
-      "id": "Log",
-      "description": "Log",
-      "type": "object",
-      "uuidPrefix": "57u5n",
-      "properties": {
-        "uuid": {
+        "container_image": {
           "type": "string"
         },
-        "etag": {
-          "type": "string",
-          "description": "Object version."
+        "progress": {
+          "type": "float"
         },
-        "id": {
+        "priority": {
           "type": "integer"
         },
-        "owner_uuid": {
-          "type": "string"
+        "exit_code": {
+          "type": "integer"
         },
-        "modified_by_client_uuid": {
+        "auth_uuid": {
           "type": "string"
         },
-        "modified_by_user_uuid": {
+        "locked_by_uuid": {
           "type": "string"
         },
-        "object_uuid": {
-          "type": "string"
+        "scheduling_parameters": {
+          "type": "Hash"
         },
-        "event_at": {
-          "type": "datetime"
+        "runtime_status": {
+          "type": "Hash"
         },
-        "event_type": {
+        "runtime_user_uuid": {
+          "type": "text"
+        },
+        "runtime_auth_scopes": {
+          "type": "Array"
+        },
+        "lock_count": {
+          "type": "integer"
+        },
+        "gateway_address": {
           "type": "string"
         },
-        "summary": {
-          "type": "text"
+        "interactive_session_started": {
+          "type": "boolean"
         },
-        "properties": {
-          "type": "Hash"
+        "output_storage_classes": {
+          "type": "Array"
         },
-        "created_at": {
-          "type": "datetime"
+        "output_properties": {
+          "type": "Hash"
         },
-        "modified_at": {
-          "type": "datetime"
+        "cost": {
+          "type": "float"
         },
-        "object_owner_uuid": {
-          "type": "string"
+        "subrequests_cost": {
+          "type": "float"
         }
       }
     },
-    "NodeList": {
-      "id": "NodeList",
-      "description": "Node list",
+    "ContainerRequestList": {
+      "id": "ContainerRequestList",
+      "description": "ContainerRequest list",
       "type": "object",
       "properties": {
         "kind": {
           "type": "string",
-          "description": "Object type. Always arvados#nodeList.",
-          "default": "arvados#nodeList"
+          "description": "Object type. Always arvados#containerRequestList.",
+          "default": "arvados#containerRequestList"
         },
         "etag": {
           "type": "string",
@@ -10516,18 +6084,18 @@
         },
         "items": {
           "type": "array",
-          "description": "The list of Nodes.",
+          "description": "The list of ContainerRequests.",
           "items": {
-            "$ref": "Node"
+            "$ref": "ContainerRequest"
           }
         },
         "next_link": {
           "type": "string",
-          "description": "A link to the next page of Nodes."
+          "description": "A link to the next page of ContainerRequests."
         },
         "next_page_token": {
           "type": "string",
-          "description": "The page token for the next page of Nodes."
+          "description": "The page token for the next page of ContainerRequests."
         },
         "selfLink": {
           "type": "string",
@@ -10535,11 +6103,11 @@
         }
       }
     },
-    "Node": {
-      "id": "Node",
-      "description": "Node",
+    "ContainerRequest": {
+      "id": "ContainerRequest",
+      "description": "ContainerRequest",
       "type": "object",
-      "uuidPrefix": "7ekkf",
+      "uuidPrefix": "xvhdp",
       "properties": {
         "uuid": {
           "type": "string"
@@ -10554,139 +6122,107 @@
         "created_at": {
           "type": "datetime"
         },
-        "modified_by_client_uuid": {
-          "type": "string"
-        },
-        "modified_by_user_uuid": {
-          "type": "string"
-        },
         "modified_at": {
           "type": "datetime"
         },
-        "slot_number": {
-          "type": "integer"
-        },
-        "hostname": {
+        "modified_by_client_uuid": {
           "type": "string"
         },
-        "domain": {
+        "modified_by_user_uuid": {
           "type": "string"
         },
-        "ip_address": {
+        "name": {
           "type": "string"
         },
-        "last_ping_at": {
-          "type": "datetime"
+        "description": {
+          "type": "text"
         },
         "properties": {
           "type": "Hash"
         },
-        "job_uuid": {
+        "state": {
           "type": "string"
-        }
-      }
-    },
-    "PipelineInstanceList": {
-      "id": "PipelineInstanceList",
-      "description": "PipelineInstance list",
-      "type": "object",
-      "properties": {
-        "kind": {
-          "type": "string",
-          "description": "Object type. Always arvados#pipelineInstanceList.",
-          "default": "arvados#pipelineInstanceList"
         },
-        "etag": {
-          "type": "string",
-          "description": "List version."
+        "requesting_container_uuid": {
+          "type": "string"
         },
-        "items": {
-          "type": "array",
-          "description": "The list of PipelineInstances.",
-          "items": {
-            "$ref": "PipelineInstance"
-          }
+        "container_uuid": {
+          "type": "string"
         },
-        "next_link": {
-          "type": "string",
-          "description": "A link to the next page of PipelineInstances."
+        "container_count_max": {
+          "type": "integer"
         },
-        "next_page_token": {
-          "type": "string",
-          "description": "The page token for the next page of PipelineInstances."
+        "mounts": {
+          "type": "Hash"
         },
-        "selfLink": {
-          "type": "string",
-          "description": "A link back to this list."
-        }
-      }
-    },
-    "PipelineInstance": {
-      "id": "PipelineInstance",
-      "description": "PipelineInstance",
-      "type": "object",
-      "uuidPrefix": "d1hrv",
-      "properties": {
-        "uuid": {
+        "runtime_constraints": {
+          "type": "Hash"
+        },
+        "container_image": {
           "type": "string"
         },
-        "etag": {
-          "type": "string",
-          "description": "Object version."
+        "environment": {
+          "type": "Hash"
         },
-        "owner_uuid": {
+        "cwd": {
           "type": "string"
         },
-        "created_at": {
-          "type": "datetime"
+        "command": {
+          "type": "Array"
         },
-        "modified_by_client_uuid": {
+        "output_path": {
           "type": "string"
         },
-        "modified_by_user_uuid": {
-          "type": "string"
+        "priority": {
+          "type": "integer"
         },
-        "modified_at": {
+        "expires_at": {
           "type": "datetime"
         },
-        "pipeline_template_uuid": {
-          "type": "string"
+        "filters": {
+          "type": "text"
         },
-        "name": {
-          "type": "string"
+        "container_count": {
+          "type": "integer"
         },
-        "components": {
-          "type": "Hash"
+        "use_existing": {
+          "type": "boolean"
         },
-        "properties": {
+        "scheduling_parameters": {
           "type": "Hash"
         },
-        "state": {
+        "output_uuid": {
           "type": "string"
         },
-        "components_summary": {
-          "type": "Hash"
+        "log_uuid": {
+          "type": "string"
         },
-        "started_at": {
-          "type": "datetime"
+        "output_name": {
+          "type": "string"
         },
-        "finished_at": {
-          "type": "datetime"
+        "output_ttl": {
+          "type": "integer"
         },
-        "description": {
-          "type": "string"
+        "output_storage_classes": {
+          "type": "Array"
+        },
+        "output_properties": {
+          "type": "Hash"
+        },
+        "cumulative_cost": {
+          "type": "float"
         }
       }
     },
-    "PipelineTemplateList": {
-      "id": "PipelineTemplateList",
-      "description": "PipelineTemplate list",
+    "GroupList": {
+      "id": "GroupList",
+      "description": "Group list",
       "type": "object",
       "properties": {
         "kind": {
           "type": "string",
-          "description": "Object type. Always arvados#pipelineTemplateList.",
-          "default": "arvados#pipelineTemplateList"
+          "description": "Object type. Always arvados#groupList.",
+          "default": "arvados#groupList"
         },
         "etag": {
           "type": "string",
@@ -10694,18 +6230,18 @@
         },
         "items": {
           "type": "array",
-          "description": "The list of PipelineTemplates.",
+          "description": "The list of Groups.",
           "items": {
-            "$ref": "PipelineTemplate"
+            "$ref": "Group"
           }
         },
         "next_link": {
           "type": "string",
-          "description": "A link to the next page of PipelineTemplates."
+          "description": "A link to the next page of Groups."
         },
         "next_page_token": {
           "type": "string",
-          "description": "The page token for the next page of PipelineTemplates."
+          "description": "The page token for the next page of Groups."
         },
         "selfLink": {
           "type": "string",
@@ -10713,11 +6249,11 @@
         }
       }
     },
-    "PipelineTemplate": {
-      "id": "PipelineTemplate",
-      "description": "PipelineTemplate",
+    "Group": {
+      "id": "Group",
+      "description": "Group",
       "type": "object",
-      "uuidPrefix": "p5p6p",
+      "uuidPrefix": "j7d0g",
       "properties": {
         "uuid": {
           "type": "string"
@@ -10744,23 +6280,38 @@
         "name": {
           "type": "string"
         },
-        "components": {
+        "description": {
+          "type": "string"
+        },
+        "group_class": {
+          "type": "string"
+        },
+        "trash_at": {
+          "type": "datetime"
+        },
+        "is_trashed": {
+          "type": "boolean"
+        },
+        "delete_at": {
+          "type": "datetime"
+        },
+        "properties": {
           "type": "Hash"
         },
-        "description": {
+        "frozen_by_uuid": {
           "type": "string"
         }
       }
     },
-    "RepositoryList": {
-      "id": "RepositoryList",
-      "description": "Repository list",
+    "KeepServiceList": {
+      "id": "KeepServiceList",
+      "description": "KeepService list",
       "type": "object",
       "properties": {
         "kind": {
           "type": "string",
-          "description": "Object type. Always arvados#repositoryList.",
-          "default": "arvados#repositoryList"
+          "description": "Object type. Always arvados#keepServiceList.",
+          "default": "arvados#keepServiceList"
         },
         "etag": {
           "type": "string",
@@ -10768,18 +6319,18 @@
         },
         "items": {
           "type": "array",
-          "description": "The list of Repositories.",
+          "description": "The list of KeepServices.",
           "items": {
-            "$ref": "Repository"
+            "$ref": "KeepService"
           }
         },
         "next_link": {
           "type": "string",
-          "description": "A link to the next page of Repositories."
+          "description": "A link to the next page of KeepServices."
         },
         "next_page_token": {
           "type": "string",
-          "description": "The page token for the next page of Repositories."
+          "description": "The page token for the next page of KeepServices."
         },
         "selfLink": {
           "type": "string",
@@ -10787,11 +6338,11 @@
         }
       }
     },
-    "Repository": {
-      "id": "Repository",
-      "description": "Repository",
+    "KeepService": {
+      "id": "KeepService",
+      "description": "KeepService",
       "type": "object",
-      "uuidPrefix": "s0uqq",
+      "uuidPrefix": "bi6l4",
       "properties": {
         "uuid": {
           "type": "string"
@@ -10812,23 +6363,35 @@
         "modified_at": {
           "type": "datetime"
         },
-        "name": {
+        "service_host": {
+          "type": "string"
+        },
+        "service_port": {
+          "type": "integer"
+        },
+        "service_ssl_flag": {
+          "type": "boolean"
+        },
+        "service_type": {
           "type": "string"
         },
         "created_at": {
           "type": "datetime"
+        },
+        "read_only": {
+          "type": "boolean"
         }
       }
     },
-    "SpecimenList": {
-      "id": "SpecimenList",
-      "description": "Specimen list",
+    "LinkList": {
+      "id": "LinkList",
+      "description": "Link list",
       "type": "object",
       "properties": {
         "kind": {
           "type": "string",
-          "description": "Object type. Always arvados#specimenList.",
-          "default": "arvados#specimenList"
+          "description": "Object type. Always arvados#linkList.",
+          "default": "arvados#linkList"
         },
         "etag": {
           "type": "string",
@@ -10836,18 +6399,18 @@
         },
         "items": {
           "type": "array",
-          "description": "The list of Specimens.",
+          "description": "The list of Links.",
           "items": {
-            "$ref": "Specimen"
+            "$ref": "Link"
           }
         },
         "next_link": {
           "type": "string",
-          "description": "A link to the next page of Specimens."
+          "description": "A link to the next page of Links."
         },
         "next_page_token": {
           "type": "string",
-          "description": "The page token for the next page of Specimens."
+          "description": "The page token for the next page of Links."
         },
         "selfLink": {
           "type": "string",
@@ -10855,11 +6418,11 @@
         }
       }
     },
-    "Specimen": {
-      "id": "Specimen",
-      "description": "Specimen",
+    "Link": {
+      "id": "Link",
+      "description": "Link",
       "type": "object",
-      "uuidPrefix": "j58dm",
+      "uuidPrefix": "o0j2j",
       "properties": {
         "uuid": {
           "type": "string"
@@ -10883,7 +6446,16 @@
         "modified_at": {
           "type": "datetime"
         },
-        "material": {
+        "tail_uuid": {
+          "type": "string"
+        },
+        "link_class": {
+          "type": "string"
+        },
+        "name": {
+          "type": "string"
+        },
+        "head_uuid": {
           "type": "string"
         },
         "properties": {
@@ -10891,15 +6463,15 @@
         }
       }
     },
-    "TraitList": {
-      "id": "TraitList",
-      "description": "Trait list",
+    "LogList": {
+      "id": "LogList",
+      "description": "Log list",
       "type": "object",
       "properties": {
         "kind": {
           "type": "string",
-          "description": "Object type. Always arvados#traitList.",
-          "default": "arvados#traitList"
+          "description": "Object type. Always arvados#logList.",
+          "default": "arvados#logList"
         },
         "etag": {
           "type": "string",
@@ -10907,18 +6479,18 @@
         },
         "items": {
           "type": "array",
-          "description": "The list of Traits.",
+          "description": "The list of Logs.",
           "items": {
-            "$ref": "Trait"
+            "$ref": "Log"
           }
         },
         "next_link": {
           "type": "string",
-          "description": "A link to the next page of Traits."
+          "description": "A link to the next page of Logs."
         },
         "next_page_token": {
           "type": "string",
-          "description": "The page token for the next page of Traits."
+          "description": "The page token for the next page of Logs."
         },
         "selfLink": {
           "type": "string",
@@ -10926,11 +6498,11 @@
         }
       }
     },
-    "Trait": {
-      "id": "Trait",
-      "description": "Trait",
+    "Log": {
+      "id": "Log",
+      "description": "Log",
       "type": "object",
-      "uuidPrefix": "q1cn2",
+      "uuidPrefix": "57u5n",
       "properties": {
         "uuid": {
           "type": "string"
@@ -10939,6 +6511,9 @@
           "type": "string",
           "description": "Object version."
         },
+        "id": {
+          "type": "integer"
+        },
         "owner_uuid": {
           "type": "string"
         },
@@ -10948,17 +6523,29 @@
         "modified_by_user_uuid": {
           "type": "string"
         },
-        "modified_at": {
+        "object_uuid": {
+          "type": "string"
+        },
+        "event_at": {
           "type": "datetime"
         },
-        "name": {
+        "event_type": {
           "type": "string"
         },
+        "summary": {
+          "type": "text"
+        },
         "properties": {
           "type": "Hash"
         },
         "created_at": {
           "type": "datetime"
+        },
+        "modified_at": {
+          "type": "datetime"
+        },
+        "object_owner_uuid": {
+          "type": "string"
         }
       }
     },
diff --git a/sdk/python/tests/run_test_server.py b/sdk/python/tests/run_test_server.py
index 84b32c2513..3acc234fc5 100644
--- a/sdk/python/tests/run_test_server.py
+++ b/sdk/python/tests/run_test_server.py
@@ -328,13 +328,6 @@ def run(leave_running_atexit=False):
     if not os.path.exists('tmp/logs'):
         os.makedirs('tmp/logs')
 
-    # Install the git repository fixtures.
-    gitdir = os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'git')
-    gittarball = os.path.join(SERVICES_SRC_DIR, 'api', 'test', 'test.git.tar')
-    if not os.path.isdir(gitdir):
-        os.makedirs(gitdir)
-    subprocess.check_output(['tar', '-xC', gitdir, '-f', gittarball])
-
     # Customizing the passenger config template is the only documented
     # way to override the default passenger_stat_throttle_rate (10 s).
     # In the testing environment, we want restart.txt to take effect
@@ -524,8 +517,6 @@ def run_keep(num_servers=2, **kwargs):
 
     for d in api.keep_services().list(filters=[['service_type','=','disk']]).execute()['items']:
         api.keep_services().delete(uuid=d['uuid']).execute()
-    for d in api.keep_disks().list().execute()['items']:
-        api.keep_disks().delete(uuid=d['uuid']).execute()
 
     for d in range(0, num_servers):
         port = _start_keep(d, **kwargs)
@@ -536,9 +527,6 @@ def run_keep(num_servers=2, **kwargs):
             'service_type': 'disk',
             'service_ssl_flag': False,
         }}).execute()
-        api.keep_disks().create(body={
-            'keep_disk': {'keep_service_uuid': svc['uuid'] }
-        }).execute()
 
     # If keepproxy and/or keep-web is running, send SIGHUP to make
     # them discover the new keepstore services.
diff --git a/sdk/python/tests/test_api.py b/sdk/python/tests/test_api.py
index 0f85e5520c..2c47152b38 100644
--- a/sdk/python/tests/test_api.py
+++ b/sdk/python/tests/test_api.py
@@ -81,11 +81,11 @@ class ArvadosApiTest(run_test_server.TestCaseWithServers):
 
     def test_timestamp_inequality_filter(self):
         api = arvados.api('v1')
-        new_item = api.specimens().create(body={}).execute()
+        new_item = api.collections().create(body={}).execute()
         for operator, should_include in [
                 ['<', False], ['>', False],
                 ['<=', True], ['>=', True], ['=', True]]:
-            response = api.specimens().list(filters=[
+            response = api.collections().list(filters=[
                 ['created_at', operator, new_item['created_at']],
                 # Also filter by uuid to ensure (if it matches) it's on page 0
                 ['uuid', '=', new_item['uuid']]]).execute()
diff --git a/services/api/app/assets/stylesheets/api_client_authorizations.css.scss b/services/api/app/assets/stylesheets/api_client_authorizations.css.scss
deleted file mode 100644
index ec87eb255f..0000000000
--- a/services/api/app/assets/stylesheets/api_client_authorizations.css.scss
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the ApiClientAuthorizations controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/services/api/app/assets/stylesheets/api_clients.css.scss b/services/api/app/assets/stylesheets/api_clients.css.scss
deleted file mode 100644
index 61d7e53aa6..0000000000
--- a/services/api/app/assets/stylesheets/api_clients.css.scss
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the ApiClients controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/services/api/app/assets/stylesheets/authorized_keys.css.scss b/services/api/app/assets/stylesheets/authorized_keys.css.scss
deleted file mode 100644
index 9eeaa89f3f..0000000000
--- a/services/api/app/assets/stylesheets/authorized_keys.css.scss
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the AuthorizedKeys controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/services/api/app/assets/stylesheets/collections.css.scss b/services/api/app/assets/stylesheets/collections.css.scss
deleted file mode 100644
index 7510f173b9..0000000000
--- a/services/api/app/assets/stylesheets/collections.css.scss
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the Collections controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/services/api/app/assets/stylesheets/commit_ancestors.css.scss b/services/api/app/assets/stylesheets/commit_ancestors.css.scss
deleted file mode 100644
index 5004f86911..0000000000
--- a/services/api/app/assets/stylesheets/commit_ancestors.css.scss
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the commit_ancestors controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/services/api/app/assets/stylesheets/commits.css.scss b/services/api/app/assets/stylesheets/commits.css.scss
deleted file mode 100644
index 6b4df4d74f..0000000000
--- a/services/api/app/assets/stylesheets/commits.css.scss
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the commits controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/services/api/app/assets/stylesheets/groups.css.scss b/services/api/app/assets/stylesheets/groups.css.scss
deleted file mode 100644
index 905e72add9..0000000000
--- a/services/api/app/assets/stylesheets/groups.css.scss
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the Groups controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/services/api/app/assets/stylesheets/humans.css.scss b/services/api/app/assets/stylesheets/humans.css.scss
deleted file mode 100644
index 29668c2737..0000000000
--- a/services/api/app/assets/stylesheets/humans.css.scss
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the Humans controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/services/api/app/assets/stylesheets/job_tasks.css.scss b/services/api/app/assets/stylesheets/job_tasks.css.scss
deleted file mode 100644
index 0d4d2607bb..0000000000
--- a/services/api/app/assets/stylesheets/job_tasks.css.scss
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the JobTasks controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/services/api/app/assets/stylesheets/jobs.css.scss b/services/api/app/assets/stylesheets/jobs.css.scss
deleted file mode 100644
index 53b6ca7fbe..0000000000
--- a/services/api/app/assets/stylesheets/jobs.css.scss
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the Jobs controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/services/api/app/assets/stylesheets/keep_disks.css.scss b/services/api/app/assets/stylesheets/keep_disks.css.scss
deleted file mode 100644
index 1996f11635..0000000000
--- a/services/api/app/assets/stylesheets/keep_disks.css.scss
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the KeepDisks controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/services/api/app/assets/stylesheets/links.css.scss b/services/api/app/assets/stylesheets/links.css.scss
deleted file mode 100644
index c2e90adf09..0000000000
--- a/services/api/app/assets/stylesheets/links.css.scss
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the links controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/services/api/app/assets/stylesheets/logs.css.scss b/services/api/app/assets/stylesheets/logs.css.scss
deleted file mode 100644
index c8b22f9f5f..0000000000
--- a/services/api/app/assets/stylesheets/logs.css.scss
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the Logs controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/services/api/app/assets/stylesheets/nodes.css b/services/api/app/assets/stylesheets/nodes.css
deleted file mode 100644
index d1ce011576..0000000000
--- a/services/api/app/assets/stylesheets/nodes.css
+++ /dev/null
@@ -1,41 +0,0 @@
-/* Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: AGPL-3.0 */
-
-/*
-  Place all the styles related to the matching controller here.
-  They will automatically be included in application.css.
-*/
-.node-status {
-    /* unknown status - might be bad */
-    background: #ff8888;
-}
-.node-status-running .node-status {
-    background: #88ff88;
-}
-.node-status-missing .node-status {
-    background: #ff8888;
-}
-.node-status-terminated .node-status {
-    background: #ffffff;
-}
-
-.node-slurm-state {
-    /* unknown status - might be bad */
-    background: #ff8888;
-}
-.node-status-missing .node-slurm-state {
-    background: #ffffff;
-}
-.node-status-terminated .node-slurm-state {
-    background: #ffffff;
-}
-.node-status-running .node-slurm-state-alloc {
-    background: #88ff88;
-}
-.node-status-running .node-slurm-state-idle {
-    background: #ffbbbb;
-}
-.node-status-running .node-slurm-state-down {
-    background: #ff8888;
-}
diff --git a/services/api/app/assets/stylesheets/nodes.css.scss b/services/api/app/assets/stylesheets/nodes.css.scss
deleted file mode 100644
index a7b08612d7..0000000000
--- a/services/api/app/assets/stylesheets/nodes.css.scss
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the Nodes controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/services/api/app/assets/stylesheets/pipeline_instances.css.scss b/services/api/app/assets/stylesheets/pipeline_instances.css.scss
deleted file mode 100644
index 7292a9aa08..0000000000
--- a/services/api/app/assets/stylesheets/pipeline_instances.css.scss
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the PipelineInstances controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/services/api/app/assets/stylesheets/pipeline_templates.css.scss b/services/api/app/assets/stylesheets/pipeline_templates.css.scss
deleted file mode 100644
index 40c0cefbea..0000000000
--- a/services/api/app/assets/stylesheets/pipeline_templates.css.scss
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the PipelineTemplates controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/services/api/app/assets/stylesheets/repositories.css.scss b/services/api/app/assets/stylesheets/repositories.css.scss
deleted file mode 100644
index 1dd9a16603..0000000000
--- a/services/api/app/assets/stylesheets/repositories.css.scss
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the Repositories controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/services/api/app/assets/stylesheets/specimens.css.scss b/services/api/app/assets/stylesheets/specimens.css.scss
deleted file mode 100644
index 60d630c8ab..0000000000
--- a/services/api/app/assets/stylesheets/specimens.css.scss
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the Specimens controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/services/api/app/assets/stylesheets/traits.css.scss b/services/api/app/assets/stylesheets/traits.css.scss
deleted file mode 100644
index 7d2f7133e1..0000000000
--- a/services/api/app/assets/stylesheets/traits.css.scss
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the Traits controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/services/api/app/assets/stylesheets/virtual_machines.css.scss b/services/api/app/assets/stylesheets/virtual_machines.css.scss
deleted file mode 100644
index 4a94d45111..0000000000
--- a/services/api/app/assets/stylesheets/virtual_machines.css.scss
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the VirtualMachines controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/services/api/app/controllers/arvados/v1/collections_controller.rb b/services/api/app/controllers/arvados/v1/collections_controller.rb
index ad1771a87e..155a8e8826 100644
--- a/services/api/app/controllers/arvados/v1/collections_controller.rb
+++ b/services/api/app/controllers/arvados/v1/collections_controller.rb
@@ -172,17 +172,7 @@ class Arvados::V1::CollectionsController < ApplicationController
       end
 
       if direction == :search_up
-        # Search upstream for jobs where this locator is the output of some job
-        if !Rails.configuration.API.DisabledAPIs["jobs.list"]
-          Job.readable_by(*@read_users).where(output: loc.to_s).each do |job|
-            search_edges(visited, job.uuid, :search_up)
-          end
-
-          Job.readable_by(*@read_users).where(log: loc.to_s).each do |job|
-            search_edges(visited, job.uuid, :search_up)
-          end
-        end
-
+        # Search upstream for jobs where this locator is the output of some container
         Container.readable_by(*@read_users).where(output: loc.to_s).pluck(:uuid).each do |c_uuid|
           search_edges(visited, c_uuid, :search_up)
         end
@@ -196,17 +186,7 @@ class Arvados::V1::CollectionsController < ApplicationController
           return
         end
 
-        # Search downstream for jobs where this locator is in script_parameters
-        if !Rails.configuration.API.DisabledAPIs["jobs.list"]
-          Job.readable_by(*@read_users).where(["jobs.script_parameters like ?", "%#{loc.to_s}%"]).each do |job|
-            search_edges(visited, job.uuid, :search_down)
-          end
-
-          Job.readable_by(*@read_users).where(["jobs.docker_image_locator = ?", "#{loc.to_s}"]).each do |job|
-            search_edges(visited, job.uuid, :search_down)
-          end
-        end
-
+        # Search downstream for jobs where this locator is in mounts
         Container.readable_by(*@read_users).where([Container.full_text_trgm + " like ?", "%#{loc.to_s}%"]).select("output, log, uuid").each do |c|
           if c.output != loc.to_s && c.log != loc.to_s
             search_edges(visited, c.uuid, :search_down)
@@ -216,21 +196,7 @@ class Arvados::V1::CollectionsController < ApplicationController
     else
       # uuid is a regular Arvados UUID
       rsc = ArvadosModel::resource_class_for_uuid uuid
-      if rsc == Job
-        Job.readable_by(*@read_users).where(uuid: uuid).each do |job|
-          visited[uuid] = job.as_api_response
-          if direction == :search_up
-            # Follow upstream collections referenced in the script parameters
-            find_collections(visited, job) do |hash, col_uuid|
-              search_edges(visited, hash, :search_up) if hash
-              search_edges(visited, col_uuid, :search_up) if col_uuid
-            end
-          elsif direction == :search_down
-            # Follow downstream job output
-            search_edges(visited, job.output, direction)
-          end
-        end
-      elsif rsc == Container
+      if rsc == Container
         c = Container.readable_by(*@read_users).where(uuid: uuid).limit(1).first
         if c
           visited[uuid] = c.as_api_response
@@ -266,16 +232,6 @@ class Arvados::V1::CollectionsController < ApplicationController
           if direction == :search_up
             visited[c.uuid] = c.as_api_response
 
-            if !Rails.configuration.API.DisabledAPIs["jobs.list"]
-              Job.readable_by(*@read_users).where(output: c.portable_data_hash).each do |job|
-                search_edges(visited, job.uuid, :search_up)
-              end
-
-              Job.readable_by(*@read_users).where(log: c.portable_data_hash).each do |job|
-                search_edges(visited, job.uuid, :search_up)
-              end
-            end
-
             ContainerRequest.readable_by(*@read_users).where(output_uuid: uuid).pluck(:uuid).each do |cr_uuid|
               search_edges(visited, cr_uuid, :search_up)
             end
diff --git a/services/api/app/controllers/arvados/v1/groups_controller.rb b/services/api/app/controllers/arvados/v1/groups_controller.rb
index c362cf32d7..be73d39dd1 100644
--- a/services/api/app/controllers/arvados/v1/groups_controller.rb
+++ b/services/api/app/controllers/arvados/v1/groups_controller.rb
@@ -218,10 +218,7 @@ class Arvados::V1::GroupsController < ApplicationController
 
     request_filters = @filters
 
-    klasses = [Group,
-     Job, PipelineInstance, PipelineTemplate, ContainerRequest, Workflow,
-     Collection,
-     Human, Specimen, Trait]
+    klasses = [Group, ContainerRequest, Workflow, Collection]
 
     table_names = Hash[klasses.collect { |k| [k, k.table_name] }]
 
diff --git a/services/api/app/controllers/arvados/v1/humans_controller.rb b/services/api/app/controllers/arvados/v1/humans_controller.rb
deleted file mode 100644
index 88eee3058d..0000000000
--- a/services/api/app/controllers/arvados/v1/humans_controller.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-class Arvados::V1::HumansController < ApplicationController
-end
diff --git a/services/api/app/controllers/arvados/v1/job_tasks_controller.rb b/services/api/app/controllers/arvados/v1/job_tasks_controller.rb
deleted file mode 100644
index b960d2e9e4..0000000000
--- a/services/api/app/controllers/arvados/v1/job_tasks_controller.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-class Arvados::V1::JobTasksController < ApplicationController
-  accept_attribute_as_json :parameters, Hash
-
-  def create
-    return send_error("Unsupported legacy jobs API",
-                      status: 400)
-  end
-end
diff --git a/services/api/app/controllers/arvados/v1/jobs_controller.rb b/services/api/app/controllers/arvados/v1/jobs_controller.rb
deleted file mode 100644
index 2d6b05269d..0000000000
--- a/services/api/app/controllers/arvados/v1/jobs_controller.rb
+++ /dev/null
@@ -1,75 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-class Arvados::V1::JobsController < ApplicationController
-  accept_attribute_as_json :components, Hash
-  accept_attribute_as_json :script_parameters, Hash
-  accept_attribute_as_json :runtime_constraints, Hash
-  accept_attribute_as_json :tasks_summary, Hash
-  skip_before_action :find_object_by_uuid, :only => [:queue, :queue_size]
-  skip_before_action :render_404_if_no_object, :only => [:queue, :queue_size]
-
-  include DbCurrentTime
-
-  def create
-    return send_error("Unsupported legacy jobs API",
-                      status: 400)
-  end
-
-  def cancel
-    return send_error("Unsupported legacy jobs API",
-                      status: 400)
-  end
-
-  def lock
-    return send_error("Unsupported legacy jobs API",
-                      status: 400)
-  end
-
-  def queue
-    @objects = []
-    index
-  end
-
-  def queue_size
-    render :json => {:queue_size => 0}
-  end
-
-  def self._create_requires_parameters
-    (super rescue {}).
-      merge({
-              find_or_create: {
-                type: 'boolean', required: false, default: false,
-              },
-              filters: {
-                type: 'array', required: false,
-              },
-              minimum_script_version: {
-                type: 'string', required: false,
-              },
-              exclude_script_versions: {
-                type: 'array', required: false,
-              },
-            })
-  end
-
-  def self._queue_requires_parameters
-    self._index_requires_parameters
-  end
-
-  protected
-
-  def load_filters_param
-    begin
-      super
-      attrs = resource_attrs rescue {}
-      @filters = Job.load_job_specific_filters attrs, @filters, @read_users
-    rescue ArgumentError => error
-      send_error(error.message)
-      false
-    else
-      true
-    end
-  end
-end
diff --git a/services/api/app/controllers/arvados/v1/keep_disks_controller.rb b/services/api/app/controllers/arvados/v1/keep_disks_controller.rb
deleted file mode 100644
index b8aa09650f..0000000000
--- a/services/api/app/controllers/arvados/v1/keep_disks_controller.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-class Arvados::V1::KeepDisksController < ApplicationController
-  skip_before_action :require_auth_scope, only: :ping
-  skip_before_action :render_404_if_no_object, only: :ping
-
-  def self._ping_requires_parameters
-    {
-      uuid: {required: false},
-      ping_secret: {required: true},
-      node_uuid: {required: false},
-      filesystem_uuid: {required: false},
-      service_host: {required: false},
-      service_port: {required: true},
-      service_ssl_flag: {required: true}
-    }
-  end
-
-  def ping
-    params[:service_host] ||= request.env['REMOTE_ADDR']
-    if !params[:uuid] && current_user.andand.is_admin
-      # Create a new KeepDisk and ping it.
-      @object = KeepDisk.new(filesystem_uuid: params[:filesystem_uuid])
-      @object.save!
-
-      # In the first ping from this new filesystem_uuid, we can't
-      # expect the keep node to know the ping_secret so we made sure
-      # we got an admin token. Here we add ping_secret to params so
-      # the ping call below is properly authenticated.
-      params[:ping_secret] = @object.ping_secret
-    end
-    act_as_system_user do
-      if !@object.andand.ping(params)
-        return render_not_found "object not found"
-      end
-      # Render the :superuser view (i.e., include the ping_secret) even
-      # if !current_user.is_admin. This is safe because @object.ping's
-      # success implies the ping_secret was already known by the client.
-      send_json @object.as_api_response(:superuser)
-    end
-  end
-
-  def find_objects_for_index
-    # all users can list all keep disks
-    @objects = model_class.where('1=1')
-    super
-  end
-end
diff --git a/services/api/app/controllers/arvados/v1/nodes_controller.rb b/services/api/app/controllers/arvados/v1/nodes_controller.rb
deleted file mode 100644
index 2510fd49fa..0000000000
--- a/services/api/app/controllers/arvados/v1/nodes_controller.rb
+++ /dev/null
@@ -1,90 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-class Arvados::V1::NodesController < ApplicationController
-  skip_before_action :require_auth_scope, :only => :ping
-  skip_before_action :find_object_by_uuid, :only => :ping
-  skip_before_action :render_404_if_no_object, :only => :ping
-
-  include DbCurrentTime
-
-  def self._ping_requires_parameters
-    { ping_secret: {required: true} }
-  end
-
-  def self._create_requires_parameters
-    super.merge(
-      { assign_slot: {required: false, type: 'boolean', description: 'assign slot and hostname'} })
-  end
-
-  def self._update_requires_parameters
-    super.merge(
-      { assign_slot: {required: false, type: 'boolean', description: 'assign slot and hostname'} })
-  end
-
-  def create
-    @object = model_class.new(resource_attrs)
-    @object.assign_slot if params[:assign_slot]
-    @object.save!
-    show
-  end
-
-  def update
-    if resource_attrs[:job_uuid].is_a? String
-      @object.job_readable = readable_job_uuids([resource_attrs[:job_uuid]]).any?
-    end
-    attrs_to_update = resource_attrs.reject { |k,v|
-      [:kind, :etag, :href].index k
-    }
-    @object.update!(attrs_to_update)
-    @object.assign_slot if params[:assign_slot]
-    @object.save!
-    show
-  end
-
-  def ping
-    act_as_system_user do
-      @object = Node.where(uuid: (params[:id] || params[:uuid])).first
-      if !@object
-        return render_not_found
-      end
-      ping_data = {
-        ip: params[:local_ipv4] || request.remote_ip,
-        ec2_instance_id: params[:instance_id]
-      }
-      [:ping_secret, :total_cpu_cores, :total_ram_mb, :total_scratch_mb]
-        .each do |key|
-        ping_data[key] = params[key] if params[key]
-      end
-      @object.ping(ping_data)
-      if @object.info['ping_secret'] == params[:ping_secret]
-        send_json @object.as_api_response(:superuser)
-      else
-        raise "Invalid ping_secret after ping"
-      end
-    end
-  end
-
-  def find_objects_for_index
-    if !current_user.andand.is_admin && current_user.andand.is_active
-      # active non-admin users can list nodes that are (or were
-      # recently) working
-      @objects = model_class.where('last_ping_at >= ?', db_current_time - 1.hours)
-    end
-    super
-    if @select.nil? or @select.include? 'job_uuid'
-      job_uuids = @objects.map { |n| n[:job_uuid] }.compact
-      assoc_jobs = readable_job_uuids(job_uuids)
-      @objects.each do |node|
-        node.job_readable = assoc_jobs.include?(node[:job_uuid])
-      end
-    end
-  end
-
-  protected
-
-  def readable_job_uuids(uuids)
-    Job.readable_by(*@read_users).select(:uuid).where(uuid: uuids).map(&:uuid)
-  end
-end
diff --git a/services/api/app/controllers/arvados/v1/pipeline_instances_controller.rb b/services/api/app/controllers/arvados/v1/pipeline_instances_controller.rb
deleted file mode 100644
index 166f71049b..0000000000
--- a/services/api/app/controllers/arvados/v1/pipeline_instances_controller.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-class Arvados::V1::PipelineInstancesController < ApplicationController
-  accept_attribute_as_json :components, Hash
-  accept_attribute_as_json :properties, Hash
-  accept_attribute_as_json :components_summary, Hash
-
-  def create
-    return send_error("Unsupported legacy jobs API",
-                      status: 400)
-  end
-
-  def cancel
-    return send_error("Unsupported legacy jobs API",
-                      status: 400)
-  end
-end
diff --git a/services/api/app/controllers/arvados/v1/pipeline_templates_controller.rb b/services/api/app/controllers/arvados/v1/pipeline_templates_controller.rb
deleted file mode 100644
index 4a5e724ee6..0000000000
--- a/services/api/app/controllers/arvados/v1/pipeline_templates_controller.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-class Arvados::V1::PipelineTemplatesController < ApplicationController
-  accept_attribute_as_json :components, Hash
-
-  def create
-    return send_error("Unsupported legacy jobs API",
-                      status: 400)
-  end
-end
diff --git a/services/api/app/controllers/arvados/v1/repositories_controller.rb b/services/api/app/controllers/arvados/v1/repositories_controller.rb
deleted file mode 100644
index 9dff6227bc..0000000000
--- a/services/api/app/controllers/arvados/v1/repositories_controller.rb
+++ /dev/null
@@ -1,124 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-class Arvados::V1::RepositoriesController < ApplicationController
-  skip_before_action :find_object_by_uuid, :only => :get_all_permissions
-  skip_before_action :render_404_if_no_object, :only => :get_all_permissions
-  before_action :admin_required, :only => :get_all_permissions
-
-  def get_all_permissions
-    # user_aks is a map of {user_uuid => array of public keys}
-    user_aks = {}
-    # admins is an array of user_uuids
-    admins = []
-    User.
-      where('users.is_active = ? or users.uuid = ?', true, anonymous_user_uuid).
-      eager_load(:authorized_keys).find_each do |u|
-      user_aks[u.uuid] = u.authorized_keys.collect do |ak|
-        {
-          public_key: ak.public_key,
-          authorized_key_uuid: ak.uuid
-        }
-      end
-      admins << u.uuid if u.is_admin
-    end
-    all_group_permissions = User.all_group_permissions
-    @repo_info = {}
-    Repository.eager_load(:permissions).find_each do |repo|
-      @repo_info[repo.uuid] = {
-        uuid: repo.uuid,
-        name: repo.name,
-        push_url: repo.push_url,
-        fetch_url: repo.fetch_url,
-        user_permissions: {},
-      }
-      # evidence is an array of {name: 'can_xxx', user_uuid: 'x-y-z'},
-      # one entry for each piece of evidence we find in the permission
-      # database that establishes that a user can access this
-      # repository. Multiple entries can be added for a given user,
-      # possibly with different access levels; these will be compacted
-      # below.
-      evidence = []
-      repo.permissions.each do |perm|
-        if ArvadosModel::resource_class_for_uuid(perm.tail_uuid) == Group
-          # A group has permission. Each user who has access to this
-          # group also has access to the repository. Access level is
-          # min(group-to-repo permission, user-to-group permission).
-          user_aks.each do |user_uuid, _|
-            perm_mask = all_group_permissions[user_uuid].andand[perm.tail_uuid]
-            if not perm_mask
-              next
-            elsif perm_mask[:manage] and perm.name == 'can_manage'
-              evidence << {name: 'can_manage', user_uuid: user_uuid}
-            elsif perm_mask[:write] and ['can_manage', 'can_write'].index perm.name
-              evidence << {name: 'can_write', user_uuid: user_uuid}
-            elsif perm_mask[:read]
-              evidence << {name: 'can_read', user_uuid: user_uuid}
-            end
-          end
-        elsif user_aks.has_key?(perm.tail_uuid)
-          # A user has permission; the user exists; and either the
-          # user is active, or it's the special case of the anonymous
-          # user which is never "active" but is allowed to read
-          # content from public repositories.
-          evidence << {name: perm.name, user_uuid: perm.tail_uuid}
-        end
-      end
-      # Owner of the repository, and all admins, can do everything.
-      ([repo.owner_uuid] | admins).each do |user_uuid|
-        # Except: no permissions for inactive users, even if they own
-        # repositories.
-        next unless user_aks.has_key?(user_uuid)
-        evidence << {name: 'can_manage', user_uuid: user_uuid}
-      end
-      # Distill all the evidence about permissions on this repository
-      # into one hash per user, of the form {'can_xxx' => true, ...}.
-      # The hash is nil for a user who has no permissions at all on
-      # this particular repository.
-      evidence.each do |perm|
-        user_uuid = perm[:user_uuid]
-        user_perms = (@repo_info[repo.uuid][:user_permissions][user_uuid] ||= {})
-        user_perms[perm[:name]] = true
-      end
-    end
-    # Revisit each {'can_xxx' => true, ...} hash for some final
-    # cleanup to make life easier for the requestor.
-    #
-    # Add a 'gitolite_permissions' key alongside the 'can_xxx' keys,
-    # for the convenience of the gitolite config file generator.
-    #
-    # Add all lesser permissions when a greater permission is
-    # present. If the requestor only wants to know who can write, it
-    # only has to test for 'can_write' in the response.
-    @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['can_write'] = true
-          user_perms['can_read'] = true
-        elsif user_perms['can_write']
-          user_perms['gitolite_permissions'] = 'RW+'
-          user_perms['can_read'] = true
-        elsif user_perms['can_read']
-          user_perms['gitolite_permissions'] = 'R'
-        end
-      end
-    end
-    # The response looks like
-    #   {"kind":"...",
-    #    "repositories":[r1,r2,r3,...],
-    #    "user_keys":usermap}
-    # where each of r1,r2,r3 looks like
-    #   {"uuid":"repo-uuid-1",
-    #    "name":"username/reponame",
-    #    "push_url":"...",
-    #    "user_permissions":{"user-uuid-a":{"can_read":true,"gitolite_permissions":"R"}}}
-    # and usermap looks like
-    #   {"user-uuid-a":[{"public_key":"ssh-rsa g...","authorized_key_uuid":"ak-uuid-g"},...],
-    #    "user-uuid-b":[{"public_key":"ssh-rsa h...","authorized_key_uuid":"ak-uuid-h"},...],...}
-    send_json(kind: 'arvados#RepositoryPermissionSnapshot',
-              repositories: @repo_info.values,
-              user_keys: user_aks)
-  end
-end
diff --git a/services/api/app/controllers/arvados/v1/schema_controller.rb b/services/api/app/controllers/arvados/v1/schema_controller.rb
index 74aa4078cb..8607325aca 100644
--- a/services/api/app/controllers/arvados/v1/schema_controller.rb
+++ b/services/api/app/controllers/arvados/v1/schema_controller.rb
@@ -484,6 +484,7 @@ class Arvados::V1::SchemaController < ApplicationController
 
     Rails.configuration.API.DisabledAPIs.each do |method, _|
       ctrl, action = method.to_s.split('.', 2)
+      next if ctrl.in?(['job_tasks', 'jobs', 'keep_disks', 'nodes', 'pipeline_instances', 'pipeline_templates', 'repositories'])
       discovery[:resources][ctrl][:methods].delete(action.to_sym)
     end
     discovery
diff --git a/services/api/app/controllers/arvados/v1/specimens_controller.rb b/services/api/app/controllers/arvados/v1/specimens_controller.rb
deleted file mode 100644
index b1e50a7e3e..0000000000
--- a/services/api/app/controllers/arvados/v1/specimens_controller.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-class Arvados::V1::SpecimensController < ApplicationController
-end
diff --git a/services/api/app/controllers/arvados/v1/traits_controller.rb b/services/api/app/controllers/arvados/v1/traits_controller.rb
deleted file mode 100644
index 7aaed5c4d4..0000000000
--- a/services/api/app/controllers/arvados/v1/traits_controller.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-class Arvados::V1::TraitsController < ApplicationController
-end
diff --git a/services/api/app/controllers/arvados/v1/users_controller.rb b/services/api/app/controllers/arvados/v1/users_controller.rb
index 031dd2e4f9..418295d26a 100644
--- a/services/api/app/controllers/arvados/v1/users_controller.rb
+++ b/services/api/app/controllers/arvados/v1/users_controller.rb
@@ -123,8 +123,7 @@ class Arvados::V1::UsersController < ApplicationController
       full_repo_name = "#{@object.username}/#{params[:repo_name]}"
     end
 
-    @response = @object.setup(repo_name: full_repo_name,
-                              vm_uuid: params[:vm_uuid],
+    @response = @object.setup(vm_uuid: params[:vm_uuid],
                               send_notification_email: params[:send_notification_email])
 
     send_json kind: "arvados#HashList", items: @response.as_api_response(nil)
diff --git a/services/api/app/helpers/api_client_authorizations_helper.rb b/services/api/app/helpers/api_client_authorizations_helper.rb
deleted file mode 100644
index e1066badc8..0000000000
--- a/services/api/app/helpers/api_client_authorizations_helper.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-module ApiClientAuthorizationsHelper
-end
diff --git a/services/api/app/helpers/api_clients_helper.rb b/services/api/app/helpers/api_clients_helper.rb
deleted file mode 100644
index 9604777598..0000000000
--- a/services/api/app/helpers/api_clients_helper.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-module ApiClientsHelper
-end
diff --git a/services/api/app/helpers/authorized_keys_helper.rb b/services/api/app/helpers/authorized_keys_helper.rb
deleted file mode 100644
index 665fff7f7c..0000000000
--- a/services/api/app/helpers/authorized_keys_helper.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-module AuthorizedKeysHelper
-end
diff --git a/services/api/app/helpers/collections_helper.rb b/services/api/app/helpers/collections_helper.rb
deleted file mode 100644
index ca44f474b9..0000000000
--- a/services/api/app/helpers/collections_helper.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-module CollectionsHelper
-end
diff --git a/services/api/app/helpers/commits_helper.rb b/services/api/app/helpers/commits_helper.rb
deleted file mode 100644
index fdb83a0375..0000000000
--- a/services/api/app/helpers/commits_helper.rb
+++ /dev/null
@@ -1,270 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-module CommitsHelper
-  extend CurrentApiClient
-
-  class GitError < RequestError
-    def http_status
-      422
-    end
-  end
-
-  def self.git_check_ref_format(e)
-    if !e or e.empty? or e[0] == '-' or e[0] == '$'
-      # definitely not valid
-      false
-    else
-      `git check-ref-format --allow-onelevel #{e.shellescape}`
-      $?.success?
-    end
-  end
-
-  # Return an array of commits (each a 40-char sha1) satisfying the
-  # given criteria.
-  #
-  # Return [] if the revisions given in minimum/maximum are invalid or
-  # don't exist in the given repository.
-  #
-  # Raise ArgumentError if the given repository is invalid, does not
-  # exist, or cannot be read for any reason. (Any transient error that
-  # prevents commit ranges from resolving must raise rather than
-  # returning an empty array.)
-  #
-  # repository can be the name of a locally hosted repository or a git
-  # URL (see git-fetch(1)). Currently http, https, and git schemes are
-  # supported.
-  def self.find_commit_range repository, minimum, maximum, exclude
-    if minimum and minimum.empty?
-      minimum = nil
-    end
-
-    if minimum and !git_check_ref_format(minimum)
-      Rails.logger.warn "find_commit_range called with invalid minimum revision: '#{minimum}'"
-      return []
-    end
-
-    if maximum and !git_check_ref_format(maximum)
-      Rails.logger.warn "find_commit_range called with invalid maximum revision: '#{maximum}'"
-      return []
-    end
-
-    if !maximum
-      maximum = "HEAD"
-    end
-
-    gitdir, is_remote = git_dir_for repository
-    fetch_remote_repository gitdir, repository if is_remote
-    ENV['GIT_DIR'] = gitdir
-
-    commits = []
-
-    # Get the commit hash for the upper bound
-    max_hash = nil
-    git_max_hash_cmd = "git rev-list --max-count=1 #{maximum.shellescape} --"
-    IO.foreach("|#{git_max_hash_cmd}") do |line|
-      max_hash = line.strip
-    end
-
-    # If not found, nothing else to do
-    if !max_hash
-      Rails.logger.warn "no refs found looking for max_hash: `GIT_DIR=#{gitdir} #{git_max_hash_cmd}` returned no output"
-      return []
-    end
-
-    # If string is invalid, nothing else to do
-    if !git_check_ref_format(max_hash)
-      Rails.logger.warn "ref returned by `GIT_DIR=#{gitdir} #{git_max_hash_cmd}` was invalid for max_hash: #{max_hash}"
-      return []
-    end
-
-    resolved_exclude = nil
-    if exclude
-      resolved_exclude = []
-      exclude.each do |e|
-        if git_check_ref_format(e)
-          IO.foreach("|git rev-list --max-count=1 #{e.shellescape} --") do |line|
-            resolved_exclude.push(line.strip)
-          end
-        else
-          Rails.logger.warn "find_commit_range called with invalid exclude invalid characters: '#{exclude}'"
-          return []
-        end
-      end
-    end
-
-    if minimum
-      # Get the commit hash for the lower bound
-      min_hash = nil
-      git_min_hash_cmd = "git rev-list --max-count=1 #{minimum.shellescape} --"
-      IO.foreach("|#{git_min_hash_cmd}") do |line|
-        min_hash = line.strip
-      end
-
-      # If not found, nothing else to do
-      if !min_hash
-        Rails.logger.warn "no refs found looking for min_hash: `GIT_DIR=#{gitdir} #{git_min_hash_cmd}` returned no output"
-        return []
-      end
-
-      # If string is invalid, nothing else to do
-      if !git_check_ref_format(min_hash)
-        Rails.logger.warn "ref returned by `GIT_DIR=#{gitdir} #{git_min_hash_cmd}` was invalid for min_hash: #{min_hash}"
-        return []
-      end
-
-      # Now find all commits between them
-      IO.foreach("|git rev-list #{min_hash.shellescape}..#{max_hash.shellescape} --") do |line|
-        hash = line.strip
-        commits.push(hash) if !resolved_exclude or !resolved_exclude.include? hash
-      end
-
-      commits.push(min_hash) if !resolved_exclude or !resolved_exclude.include? min_hash
-    else
-      commits.push(max_hash) if !resolved_exclude or !resolved_exclude.include? max_hash
-    end
-
-    commits
-  end
-
-  # Given a repository (url, or name of hosted repo) and commit sha1,
-  # copy the commit into the internal git repo (if necessary), and tag
-  # it with the given tag (typically a job UUID).
-  #
-  # The repo can be a remote url, but in this case sha1 must already
-  # be present in our local cache for that repo: e.g., sha1 was just
-  # returned by find_commit_range.
-  def self.tag_in_internal_repository repo_name, sha1, tag
-    unless git_check_ref_format tag
-      raise ArgumentError.new "invalid tag #{tag}"
-    end
-    unless /^[0-9a-f]{40}$/ =~ sha1
-      raise ArgumentError.new "invalid sha1 #{sha1}"
-    end
-    src_gitdir, _ = git_dir_for repo_name
-    unless src_gitdir
-      raise ArgumentError.new "no local repository for #{repo_name}"
-    end
-    dst_gitdir = Rails.configuration.Containers.JobsAPI.GitInternalDir
-
-    begin
-      commit_in_dst = must_git(dst_gitdir, "log -n1 --format=%H #{sha1.shellescape}^{commit}").strip
-    rescue GitError
-      commit_in_dst = false
-    end
-
-    tag_cmd = "tag --force #{tag.shellescape} #{sha1.shellescape}^{commit}"
-    if commit_in_dst == sha1
-      must_git(dst_gitdir, tag_cmd)
-    else
-      # git-fetch is faster than pack-objects|unpack-objects, but
-      # git-fetch can't fetch by sha1. So we first try to fetch a
-      # branch that has the desired commit, and if that fails (there
-      # is no such branch, or the branch we choose changes under us in
-      # race), we fall back to pack|unpack.
-      begin
-        branches = must_git(src_gitdir,
-                            "branch --contains #{sha1.shellescape}")
-        m = branches.match(/^. (\w+)\n/)
-        if !m
-          raise GitError.new "commit is not on any branch"
-        end
-        branch = m[1]
-        must_git(dst_gitdir,
-                 "fetch file://#{src_gitdir.shellescape} #{branch.shellescape}")
-        # Even if all of the above steps succeeded, we might still not
-        # have the right commit due to a race, in which case tag_cmd
-        # will fail, and we'll need to fall back to pack|unpack. So
-        # don't be tempted to condense this tag_cmd and the one in the
-        # rescue block into a single attempt.
-        must_git(dst_gitdir, tag_cmd)
-      rescue GitError
-        must_pipe("echo #{sha1.shellescape}",
-                  "git --git-dir #{src_gitdir.shellescape} pack-objects -q --revs --stdout",
-                  "git --git-dir #{dst_gitdir.shellescape} unpack-objects -q")
-        must_git(dst_gitdir, tag_cmd)
-      end
-    end
-  end
-
-  protected
-
-  def self.remote_url? repo_name
-    /^(https?|git):\/\// =~ repo_name
-  end
-
-  # Return [local_git_dir, is_remote]. If is_remote, caller must use
-  # fetch_remote_repository to ensure content is up-to-date.
-  #
-  # Raises an exception if the latest content could not be fetched for
-  # any reason.
-  def self.git_dir_for repo_name
-    if remote_url? repo_name
-      return [cache_dir_for(repo_name), true]
-    end
-    repos = Repository.readable_by(current_user).where(name: repo_name)
-    if repos.count == 0
-      raise ArgumentError.new "Repository not found: '#{repo_name}'"
-    elsif repos.count > 1
-      Rails.logger.error "Multiple repositories with name=='#{repo_name}'!"
-      raise ArgumentError.new "Name conflict"
-    else
-      return [repos.first.server_path, false]
-    end
-  end
-
-  def self.cache_dir_for git_url
-    File.join(cache_dir_base, Digest::SHA1.hexdigest(git_url) + ".git").to_s
-  end
-
-  def self.cache_dir_base
-    Rails.root.join 'tmp', 'git-cache'
-  end
-
-  def self.fetch_remote_repository gitdir, git_url
-    # Caller decides which protocols are worth using. This is just a
-    # safety check to ensure we never use urls like "--flag" or wander
-    # into git's hardlink features by using bare "/path/foo" instead
-    # of "file:///path/foo".
-    unless /^[a-z]+:\/\// =~ git_url
-      raise ArgumentError.new "invalid git url #{git_url}"
-    end
-    begin
-      must_git gitdir, "branch"
-    rescue GitError => e
-      raise unless /Not a git repository/i =~ e.to_s
-      # OK, this just means we need to create a blank cache repository
-      # before fetching.
-      FileUtils.mkdir_p gitdir
-      must_git gitdir, "init"
-    end
-    must_git(gitdir,
-             "fetch --no-progress --tags --prune --force --update-head-ok #{git_url.shellescape} 'refs/heads/*:refs/heads/*'")
-  end
-
-  def self.must_git gitdir, *cmds
-    # Clear token in case a git helper tries to use it as a password.
-    orig_token = ENV['ARVADOS_API_TOKEN']
-    ENV['ARVADOS_API_TOKEN'] = ''
-    last_output = ''
-    begin
-      git = "git --git-dir #{gitdir.shellescape}"
-      cmds.each do |cmd|
-        last_output = must_pipe git+" "+cmd
-      end
-    ensure
-      ENV['ARVADOS_API_TOKEN'] = orig_token
-    end
-    return last_output
-  end
-
-  def self.must_pipe *cmds
-    cmd = cmds.join(" 2>&1 |") + " 2>&1"
-    out = IO.read("| </dev/null #{cmd}")
-    if not $?.success?
-      raise GitError.new "#{cmd}: #{$?}: #{out}"
-    end
-    return out
-  end
-end
diff --git a/services/api/app/helpers/groups_helper.rb b/services/api/app/helpers/groups_helper.rb
deleted file mode 100644
index 5464d964ff..0000000000
--- a/services/api/app/helpers/groups_helper.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-module GroupsHelper
-end
diff --git a/services/api/app/helpers/humans_helper.rb b/services/api/app/helpers/humans_helper.rb
deleted file mode 100644
index e9b4c96e22..0000000000
--- a/services/api/app/helpers/humans_helper.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-module HumansHelper
-end
diff --git a/services/api/app/helpers/job_tasks_helper.rb b/services/api/app/helpers/job_tasks_helper.rb
deleted file mode 100644
index ae78d75a15..0000000000
--- a/services/api/app/helpers/job_tasks_helper.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-module JobTasksHelper
-end
diff --git a/services/api/app/helpers/jobs_helper.rb b/services/api/app/helpers/jobs_helper.rb
deleted file mode 100644
index ba3f715e61..0000000000
--- a/services/api/app/helpers/jobs_helper.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-module JobsHelper
-end
diff --git a/services/api/app/helpers/keep_disks_helper.rb b/services/api/app/helpers/keep_disks_helper.rb
deleted file mode 100644
index 19386c9087..0000000000
--- a/services/api/app/helpers/keep_disks_helper.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-module KeepDisksHelper
-end
diff --git a/services/api/app/helpers/links_helper.rb b/services/api/app/helpers/links_helper.rb
deleted file mode 100644
index 422a42d540..0000000000
--- a/services/api/app/helpers/links_helper.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-module LinksHelper
-end
diff --git a/services/api/app/helpers/logs_helper.rb b/services/api/app/helpers/logs_helper.rb
deleted file mode 100644
index 9b767d3791..0000000000
--- a/services/api/app/helpers/logs_helper.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-module LogsHelper
-end
diff --git a/services/api/app/helpers/nodes_helper.rb b/services/api/app/helpers/nodes_helper.rb
deleted file mode 100644
index cd1ecc9345..0000000000
--- a/services/api/app/helpers/nodes_helper.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-module NodesHelper
-end
diff --git a/services/api/app/helpers/pipeline_instances_helper.rb b/services/api/app/helpers/pipeline_instances_helper.rb
deleted file mode 100644
index 79c2e09c2c..0000000000
--- a/services/api/app/helpers/pipeline_instances_helper.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-module PipelineInstancesHelper
-end
diff --git a/services/api/app/helpers/pipeline_templates_helper.rb b/services/api/app/helpers/pipeline_templates_helper.rb
deleted file mode 100644
index 135b525d4a..0000000000
--- a/services/api/app/helpers/pipeline_templates_helper.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-module PipelineTemplatesHelper
-end
diff --git a/services/api/app/helpers/repositories_helper.rb b/services/api/app/helpers/repositories_helper.rb
deleted file mode 100644
index 04ef0f1860..0000000000
--- a/services/api/app/helpers/repositories_helper.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-module RepositoriesHelper
-end
diff --git a/services/api/app/helpers/specimens_helper.rb b/services/api/app/helpers/specimens_helper.rb
deleted file mode 100644
index 5c7e98ab33..0000000000
--- a/services/api/app/helpers/specimens_helper.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-module SpecimensHelper
-end
diff --git a/services/api/app/helpers/traits_helper.rb b/services/api/app/helpers/traits_helper.rb
deleted file mode 100644
index 35ae11d00d..0000000000
--- a/services/api/app/helpers/traits_helper.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-module TraitsHelper
-end
diff --git a/services/api/app/helpers/virtual_machines_helper.rb b/services/api/app/helpers/virtual_machines_helper.rb
deleted file mode 100644
index 7d2b08f87d..0000000000
--- a/services/api/app/helpers/virtual_machines_helper.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-module VirtualMachinesHelper
-end
diff --git a/services/api/app/models/human.rb b/services/api/app/models/human.rb
deleted file mode 100644
index 68972825f9..0000000000
--- a/services/api/app/models/human.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-class Human < ArvadosModel
-  include HasUuid
-  include KindAndEtag
-  include CommonApiTemplate
-  serialize :properties, Hash
-
-  api_accessible :user, extend: :common do |t|
-    t.add :properties
-  end
-end
diff --git a/services/api/app/models/job.rb b/services/api/app/models/job.rb
deleted file mode 100644
index 029a313285..0000000000
--- a/services/api/app/models/job.rb
+++ /dev/null
@@ -1,564 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-#
-#
-# Legacy jobs API aka crunch v1
-#
-# This is superceded by containers / container_requests (aka crunch v2)
-#
-# Arvados installations since the end of 2017 should have never
-# used jobs, and are unaffected by this change.
-#
-# So that older Arvados sites don't lose access to legacy records, the
-# API has been converted to read-only.  Creating and updating jobs
-# (and related types job_task, pipeline_template and
-# pipeline_instance) is disabled and much of the business logic
-# related has been removed, along with the crunch-dispatch.rb and
-# various other code specific to the jobs API.
-#
-# If you need to resurrect any of this code, here is the last commit
-# on master before the branch removing jobs API support:
-#
-# Wed Aug 7 14:49:38 2019 -0400 07d92519438a592d531f2c7558cd51788da262ca
-
-require 'log_reuse_info'
-require 'safe_json'
-
-class Job < ArvadosModel
-  include HasUuid
-  include KindAndEtag
-  include CommonApiTemplate
-  extend CurrentApiClient
-  extend LogReuseInfo
-  serialize :components, Hash
-  serialize :script_parameters, Hash
-  serialize :runtime_constraints, Hash
-  serialize :tasks_summary, Hash
-  before_create :ensure_unique_submit_id
-  before_validation :set_priority
-  before_validation :update_state_from_old_state_attrs
-  before_validation :update_script_parameters_digest
-  validate :ensure_script_version_is_commit
-  validate :find_docker_image_locator
-  validate :find_arvados_sdk_version
-  validate :validate_status
-  validate :validate_state_change
-  validate :ensure_no_collection_uuids_in_script_params
-  before_save :tag_version_in_internal_repository
-  before_save :update_timestamps_when_state_changes
-  before_create :create_disabled
-  before_update :update_disabled
-
-  has_many(:nodes, foreign_key: 'job_uuid', primary_key: 'uuid')
-
-  class SubmitIdReused < RequestError
-  end
-
-  api_accessible :user, extend: :common do |t|
-    t.add :submit_id
-    t.add :priority
-    t.add :script
-    t.add :script_parameters
-    t.add :script_version
-    t.add :cancelled_at
-    t.add :cancelled_by_client_uuid
-    t.add :cancelled_by_user_uuid
-    t.add :started_at
-    t.add :finished_at
-    t.add :output
-    t.add :success
-    t.add :running
-    t.add :state
-    t.add :is_locked_by_uuid
-    t.add :log
-    t.add :runtime_constraints
-    t.add :tasks_summary
-    t.add :nondeterministic
-    t.add :repository
-    t.add :supplied_script_version
-    t.add :arvados_sdk_version
-    t.add :docker_image_locator
-    t.add :queue_position
-    t.add :node_uuids
-    t.add :description
-    t.add :components
-  end
-
-  # Supported states for a job
-  States = [
-            (Queued = 'Queued'),
-            (Running = 'Running'),
-            (Cancelled = 'Cancelled'),
-            (Failed = 'Failed'),
-            (Complete = 'Complete'),
-           ]
-
-  after_initialize do
-    @need_crunch_dispatch_trigger = false
-  end
-
-  def self.limit_index_columns_read
-    ["components"]
-  end
-
-  def self.protected_attributes
-    [:arvados_sdk_version, :docker_image_locator]
-  end
-
-  def assert_finished
-    update(finished_at: finished_at || db_current_time,
-                      success: success.nil? ? false : success,
-                      running: false)
-  end
-
-  def node_uuids
-    nodes.map(&:uuid)
-  end
-
-  def self.queue
-    self.where('state = ?', Queued).order('priority desc, created_at')
-  end
-
-  def queue_position
-    # We used to report this accurately, but the implementation made queue
-    # API requests O(n**2) for the size of the queue.  See #8800.
-    # We've soft-disabled it because it's not clear we even want this
-    # functionality: now that we have Node Manager with support for multiple
-    # node sizes, "queue position" tells you very little about when a job will
-    # run.
-    state == Queued ? 0 : nil
-  end
-
-  def self.running
-    self.where('running = ?', true).
-      order('priority desc, created_at')
-  end
-
-  def lock locked_by_uuid
-    with_lock do
-      unless self.state == Queued and self.is_locked_by_uuid.nil?
-        raise AlreadyLockedError
-      end
-      self.state = Running
-      self.is_locked_by_uuid = locked_by_uuid
-      self.save!
-    end
-  end
-
-  def update_script_parameters_digest
-    self.script_parameters_digest = self.class.sorted_hash_digest(script_parameters)
-  end
-
-  def self.searchable_columns operator
-    super - ["script_parameters_digest"]
-  end
-
-  def self.full_text_searchable_columns
-    super - ["script_parameters_digest"]
-  end
-
-  def self.load_job_specific_filters attrs, orig_filters, read_users
-    # Convert Job-specific @filters entries into general SQL filters.
-    script_info = {"repository" => nil, "script" => nil}
-    git_filters = Hash.new do |hash, key|
-      hash[key] = {"max_version" => "HEAD", "exclude_versions" => []}
-    end
-    filters = []
-    orig_filters.each do |attr, operator, operand|
-      if (script_info.has_key? attr) and (operator == "=")
-        if script_info[attr].nil?
-          script_info[attr] = operand
-        elsif script_info[attr] != operand
-          raise ArgumentError.new("incompatible #{attr} filters")
-        end
-      end
-      case operator
-      when "in git"
-        git_filters[attr]["min_version"] = operand
-      when "not in git"
-        git_filters[attr]["exclude_versions"] += Array.wrap(operand)
-      when "in docker", "not in docker"
-        image_hashes = Array.wrap(operand).flat_map do |search_term|
-          image_search, image_tag = search_term.split(':', 2)
-          Collection.
-            find_all_for_docker_image(image_search, image_tag, read_users, filter_compatible_format: false).
-            map(&:portable_data_hash)
-        end
-        filters << [attr, operator.sub(/ docker$/, ""), image_hashes]
-      else
-        filters << [attr, operator, operand]
-      end
-    end
-
-    # Build a real script_version filter from any "not? in git" filters.
-    git_filters.each_pair do |attr, filter|
-      case attr
-      when "script_version"
-        script_info.each_pair do |key, value|
-          if value.nil?
-            raise ArgumentError.new("script_version filter needs #{key} filter")
-          end
-        end
-        filter["repository"] = script_info["repository"]
-        if attrs[:script_version]
-          filter["max_version"] = attrs[:script_version]
-        else
-          # Using HEAD, set earlier by the hash default, is fine.
-        end
-      when "arvados_sdk_version"
-        filter["repository"] = "arvados"
-      else
-        raise ArgumentError.new("unknown attribute for git filter: #{attr}")
-      end
-      revisions = CommitsHelper::find_commit_range(filter["repository"],
-                                           filter["min_version"],
-                                           filter["max_version"],
-                                           filter["exclude_versions"])
-      if revisions.empty?
-        raise ArgumentError.
-          new("error searching #{filter['repository']} from " +
-              "'#{filter['min_version']}' to '#{filter['max_version']}', " +
-              "excluding #{filter['exclude_versions']}")
-      end
-      filters.append([attr, "in", revisions])
-    end
-
-    filters
-  end
-
-  def self.default_git_filters(attr_name, repo_name, refspec)
-    # Add a filter to @filters for `attr_name` = the latest commit available
-    # in `repo_name` at `refspec`.  No filter is added if refspec can't be
-    # resolved.
-    commits = CommitsHelper::find_commit_range(repo_name, nil, refspec, nil)
-    if commit_hash = commits.first
-      [[attr_name, "=", commit_hash]]
-    else
-      []
-    end
-  end
-
-  def cancel(cascade: false, need_transaction: true)
-    raise "No longer supported"
-  end
-
-  protected
-
-  def self.sorted_hash_digest h
-    Digest::MD5.hexdigest(Oj.dump(deep_sort_hash(h)))
-  end
-
-  def foreign_key_attributes
-    super + %w(output log)
-  end
-
-  def skip_uuid_read_permission_check
-    super + %w(cancelled_by_client_uuid)
-  end
-
-  def skip_uuid_existence_check
-    super + %w(output log)
-  end
-
-  def set_priority
-    if self.priority.nil?
-      self.priority = 0
-    end
-    true
-  end
-
-  def ensure_script_version_is_commit
-    if state == Running
-      # Apparently client has already decided to go for it. This is
-      # needed to run a local job using a local working directory
-      # instead of a commit-ish.
-      return true
-    end
-    if new_record? or repository_changed? or script_version_changed?
-      sha1 = CommitsHelper::find_commit_range(repository,
-                                      nil, script_version, nil).first
-      if not sha1
-        errors.add :script_version, "#{script_version} does not resolve to a commit"
-        return false
-      end
-      if supplied_script_version.nil? or supplied_script_version.empty?
-        self.supplied_script_version = script_version
-      end
-      self.script_version = sha1
-    end
-    true
-  end
-
-  def tag_version_in_internal_repository
-    if state == Running
-      # No point now. See ensure_script_version_is_commit.
-      true
-    elsif errors.any?
-      # Won't be saved, and script_version might not even be valid.
-      true
-    elsif new_record? or repository_changed? or script_version_changed?
-      uuid_was = uuid
-      begin
-        assign_uuid
-        CommitsHelper::tag_in_internal_repository repository, script_version, uuid
-      rescue
-        self.uuid = uuid_was
-        raise
-      end
-    end
-  end
-
-  def ensure_unique_submit_id
-    if !submit_id.nil?
-      if Job.where('submit_id=?',self.submit_id).first
-        raise SubmitIdReused.new
-      end
-    end
-    true
-  end
-
-  def resolve_runtime_constraint(key, attr_sym)
-    if ((runtime_constraints.is_a? Hash) and
-        (search = runtime_constraints[key]))
-      ok, result = yield search
-    else
-      ok, result = true, nil
-    end
-    if ok
-      send("#{attr_sym}=".to_sym, result)
-    else
-      errors.add(attr_sym, result)
-    end
-    ok
-  end
-
-  def find_arvados_sdk_version
-    resolve_runtime_constraint("arvados_sdk_version",
-                               :arvados_sdk_version) do |git_search|
-      commits = CommitsHelper::find_commit_range("arvados",
-                                         nil, git_search, nil)
-      if commits.empty?
-        [false, "#{git_search} does not resolve to a commit"]
-      elsif not runtime_constraints["docker_image"]
-        [false, "cannot be specified without a Docker image constraint"]
-      else
-        [true, commits.first]
-      end
-    end
-  end
-
-  def find_docker_image_locator
-    if runtime_constraints.is_a? Hash and Rails.configuration.Containers.JobsAPI.DefaultDockerImage != ""
-      runtime_constraints['docker_image'] ||=
-        Rails.configuration.Containers.JobsAPI.DefaultDockerImage
-    end
-
-    resolve_runtime_constraint("docker_image",
-                               :docker_image_locator) do |image_search|
-      image_tag = runtime_constraints['docker_image_tag']
-      if coll = Collection.for_latest_docker_image(image_search, image_tag)
-        [true, coll.portable_data_hash]
-      else
-        [false, "not found for #{image_search}"]
-      end
-    end
-  end
-
-  def permission_to_update
-    if is_locked_by_uuid_was and !(current_user and
-                                   (current_user.uuid == is_locked_by_uuid_was or
-                                    current_user.uuid == system_user.uuid))
-      if script_changed? or
-          script_parameters_changed? or
-          script_version_changed? or
-          (!cancelled_at_was.nil? and
-           (cancelled_by_client_uuid_changed? or
-            cancelled_by_user_uuid_changed? or
-            cancelled_at_changed?)) or
-          started_at_changed? or
-          finished_at_changed? or
-          running_changed? or
-          success_changed? or
-          output_changed? or
-          log_changed? or
-          tasks_summary_changed? or
-          (state_changed? && state != Cancelled) or
-          components_changed?
-        logger.warn "User #{current_user.uuid if current_user} tried to change protected job attributes on locked #{self.class.to_s} #{uuid_was}"
-        return false
-      end
-    end
-    if !is_locked_by_uuid_changed?
-      super
-    else
-      if !current_user
-        logger.warn "Anonymous user tried to change lock on #{self.class.to_s} #{uuid_was}"
-        false
-      elsif is_locked_by_uuid_was and is_locked_by_uuid_was != current_user.uuid
-        logger.warn "User #{current_user.uuid} tried to steal lock on #{self.class.to_s} #{uuid_was} from #{is_locked_by_uuid_was}"
-        false
-      elsif !is_locked_by_uuid.nil? and is_locked_by_uuid != current_user.uuid
-        logger.warn "User #{current_user.uuid} tried to lock #{self.class.to_s} #{uuid_was} with uuid #{is_locked_by_uuid}"
-        false
-      else
-        super
-      end
-    end
-  end
-
-  def update_modified_by_fields
-    if self.cancelled_at_changed?
-      # Ensure cancelled_at cannot be set to arbitrary non-now times,
-      # or changed once it is set.
-      if self.cancelled_at and not self.cancelled_at_was
-        self.cancelled_at = db_current_time
-        self.cancelled_by_user_uuid = current_user.uuid
-        self.cancelled_by_client_uuid = current_api_client.andand.uuid
-        @need_crunch_dispatch_trigger = true
-      else
-        self.cancelled_at = self.cancelled_at_was
-        self.cancelled_by_user_uuid = self.cancelled_by_user_uuid_was
-        self.cancelled_by_client_uuid = self.cancelled_by_client_uuid_was
-      end
-    end
-    super
-  end
-
-  def update_timestamps_when_state_changes
-    return if not (state_changed? or new_record?)
-
-    case state
-    when Running
-      self.started_at ||= db_current_time
-    when Failed, Complete
-      self.finished_at ||= db_current_time
-    when Cancelled
-      self.cancelled_at ||= db_current_time
-    end
-
-    # TODO: Remove the following case block when old "success" and
-    # "running" attrs go away. Until then, this ensures we still
-    # expose correct success/running flags to older clients, even if
-    # some new clients are writing only the new state attribute.
-    case state
-    when Queued
-      self.running = false
-      self.success = nil
-    when Running
-      self.running = true
-      self.success = nil
-    when Cancelled, Failed
-      self.running = false
-      self.success = false
-    when Complete
-      self.running = false
-      self.success = true
-    end
-    self.running ||= false # Default to false instead of nil.
-
-    @need_crunch_dispatch_trigger = true
-
-    true
-  end
-
-  def update_state_from_old_state_attrs
-    # If a client has touched the legacy state attrs, update the
-    # "state" attr to agree with the updated values of the legacy
-    # attrs.
-    #
-    # TODO: Remove this method when old "success" and "running" attrs
-    # go away.
-    if cancelled_at_changed? or
-        success_changed? or
-        running_changed? or
-        state.nil?
-      if cancelled_at
-        self.state = Cancelled
-      elsif success == false
-        self.state = Failed
-      elsif success == true
-        self.state = Complete
-      elsif running == true
-        self.state = Running
-      else
-        self.state = Queued
-      end
-    end
-    true
-  end
-
-  def validate_status
-    if self.state.in?(States)
-      true
-    else
-      errors.add :state, "#{state.inspect} must be one of: #{States.inspect}"
-      false
-    end
-  end
-
-  def validate_state_change
-    ok = true
-    if self.state_changed?
-      ok = case self.state_was
-           when nil
-             # state isn't set yet
-             true
-           when Queued
-             # Permit going from queued to any state
-             true
-           when Running
-             # From running, may only transition to a finished state
-             [Complete, Failed, Cancelled].include? self.state
-           when Complete, Failed, Cancelled
-             # Once in a finished state, don't permit any more state changes
-             false
-           else
-             # Any other state transition is also invalid
-             false
-           end
-      if not ok
-        errors.add :state, "invalid change from #{self.state_was} to #{self.state}"
-      end
-    end
-    ok
-  end
-
-  def ensure_no_collection_uuids_in_script_params
-    # Fail validation if any script_parameters field includes a string containing a
-    # collection uuid pattern.
-    if self.script_parameters_changed?
-      if recursive_hash_search(self.script_parameters, Collection.uuid_regex)
-        self.errors.add :script_parameters, "must use portable_data_hash instead of collection uuid"
-        return false
-      end
-    end
-    true
-  end
-
-  # recursive_hash_search searches recursively through hashes and
-  # arrays in 'thing' for string fields matching regular expression
-  # 'pattern'.  Returns true if pattern is found, false otherwise.
-  def recursive_hash_search thing, pattern
-    if thing.is_a? Hash
-      thing.each do |k, v|
-        return true if recursive_hash_search v, pattern
-      end
-    elsif thing.is_a? Array
-      thing.each do |k|
-        return true if recursive_hash_search k, pattern
-      end
-    elsif thing.is_a? String
-      return true if thing.match pattern
-    end
-    false
-  end
-
-  def create_disabled
-    raise "Disabled"
-  end
-
-  def update_disabled
-    raise "Disabled"
-  end
-end
diff --git a/services/api/app/models/job_task.rb b/services/api/app/models/job_task.rb
deleted file mode 100644
index b181e76ccf..0000000000
--- a/services/api/app/models/job_task.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-class JobTask < ArvadosModel
-  include HasUuid
-  include KindAndEtag
-  include CommonApiTemplate
-  serialize :parameters, Hash
-  before_create :set_default_qsequence
-  after_update :delete_created_job_tasks_if_failed
-  before_create :create_disabled
-  before_update :update_disabled
-
-  api_accessible :user, extend: :common do |t|
-    t.add :job_uuid
-    t.add :created_by_job_task_uuid
-    t.add :sequence
-    t.add :qsequence
-    t.add :parameters
-    t.add :output
-    t.add :progress
-    t.add :success
-    t.add :started_at
-    t.add :finished_at
-  end
-
-  protected
-
-  def delete_created_job_tasks_if_failed
-    if self.success == false and self.success != self.success_was
-      JobTask.delete_all ['created_by_job_task_uuid = ?', self.uuid]
-    end
-  end
-
-  def set_default_qsequence
-    self.qsequence ||= self.class.connection.
-      select_value("SELECT nextval('job_tasks_qsequence_seq')")
-  end
-
-  def create_disabled
-    raise "Disabled"
-  end
-
-  def update_disabled
-    raise "Disabled"
-  end
-end
diff --git a/services/api/app/models/keep_disk.rb b/services/api/app/models/keep_disk.rb
deleted file mode 100644
index 589936f845..0000000000
--- a/services/api/app/models/keep_disk.rb
+++ /dev/null
@@ -1,79 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-class KeepDisk < ArvadosModel
-  include HasUuid
-  include KindAndEtag
-  include CommonApiTemplate
-  before_validation :ensure_ping_secret
-
-  api_accessible :user, extend: :common do |t|
-    t.add :node_uuid
-    t.add :filesystem_uuid
-    t.add :bytes_total
-    t.add :bytes_free
-    t.add :is_readable
-    t.add :is_writable
-    t.add :last_read_at
-    t.add :last_write_at
-    t.add :last_ping_at
-    t.add :service_host
-    t.add :service_port
-    t.add :service_ssl_flag
-    t.add :keep_service_uuid
-  end
-  api_accessible :superuser, :extend => :user do |t|
-    t.add :ping_secret
-  end
-
-  def foreign_key_attributes
-    super.reject { |a| a == "filesystem_uuid" }
-  end
-
-  def ping(o)
-    raise "must have :service_host and :ping_secret" unless o[:service_host] and o[:ping_secret]
-
-    if o[:ping_secret] != self.ping_secret
-      logger.info "Ping: secret mismatch: received \"#{o[:ping_secret]}\" != \"#{self.ping_secret}\""
-      return nil
-    end
-
-    @bypass_arvados_authorization = true
-    self.update!(o.select { |k,v|
-                             [:bytes_total,
-                              :bytes_free,
-                              :is_readable,
-                              :is_writable,
-                              :last_read_at,
-                              :last_write_at
-                             ].collect(&:to_s).index k
-                           }.merge(last_ping_at: db_current_time))
-  end
-
-  def service_host
-    KeepService.find_by_uuid(self.keep_service_uuid).andand.service_host
-  end
-
-  def service_port
-    KeepService.find_by_uuid(self.keep_service_uuid).andand.service_port
-  end
-
-  def service_ssl_flag
-    KeepService.find_by_uuid(self.keep_service_uuid).andand.service_ssl_flag
-  end
-
-  protected
-
-  def ensure_ping_secret
-    self.ping_secret ||= rand(2**256).to_s(36)
-  end
-
-  def permission_to_update
-    @bypass_arvados_authorization or super
-  end
-
-  def permission_to_create
-    current_user and current_user.is_admin
-  end
-end
diff --git a/services/api/app/models/node.rb b/services/api/app/models/node.rb
deleted file mode 100644
index f384ba582b..0000000000
--- a/services/api/app/models/node.rb
+++ /dev/null
@@ -1,295 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'tempfile'
-
-class Node < ArvadosModel
-  include HasUuid
-  include KindAndEtag
-  include CommonApiTemplate
-
-  # Posgresql JSONB columns should NOT be declared as serialized, Rails 5
-  # already know how to properly treat them.
-  attribute :properties, :jsonbHash, default: {}
-  attribute :info, :jsonbHash, default: {}
-
-  before_validation :ensure_ping_secret
-  after_update :dns_server_update
-
-  # Only a controller can figure out whether or not the current API tokens
-  # have access to the associated Job.  They're expected to set
-  # job_readable=true if the Job UUID can be included in the API response.
-  belongs_to :job,
-             foreign_key: 'job_uuid',
-             primary_key: 'uuid',
-             optional: true
-  attr_accessor :job_readable
-
-  UNUSED_NODE_IP = '127.40.4.0'
-  MAX_VMS = 3
-
-  api_accessible :user, :extend => :common do |t|
-    t.add :hostname
-    t.add :domain
-    t.add :ip_address
-    t.add :last_ping_at
-    t.add :slot_number
-    t.add :status
-    t.add :api_job_uuid, as: :job_uuid
-    t.add :crunch_worker_state
-    t.add :properties
-  end
-  api_accessible :superuser, :extend => :user do |t|
-    t.add :first_ping_at
-    t.add :info
-    t.add lambda { |x| Rails.configuration.Containers.SLURM.Managed.ComputeNodeNameservers.keys }, :as => :nameservers
-  end
-
-  after_initialize do
-    @bypass_arvados_authorization = false
-  end
-
-  def domain
-    super || Rails.configuration.Containers.SLURM.Managed.ComputeNodeDomain
-  end
-
-  def api_job_uuid
-    job_readable ? job_uuid : nil
-  end
-
-  def crunch_worker_state
-    return 'down' if slot_number.nil?
-    case self.info.andand['slurm_state']
-    when 'alloc', 'comp', 'mix', 'drng'
-      'busy'
-    when 'idle'
-      'idle'
-    else
-      'down'
-    end
-  end
-
-  def status
-    if !self.last_ping_at
-      if db_current_time - self.created_at > 5.minutes
-        'startup-fail'
-      else
-        'pending'
-      end
-    elsif db_current_time - self.last_ping_at > 1.hours
-      'missing'
-    else
-      'running'
-    end
-  end
-
-  def ping(o)
-    raise "must have :ip and :ping_secret" unless o[:ip] and o[:ping_secret]
-
-    if o[:ping_secret] != self.info['ping_secret']
-      logger.info "Ping: secret mismatch: received \"#{o[:ping_secret]}\" != \"#{self.info['ping_secret']}\""
-      raise ArvadosModel::UnauthorizedError.new("Incorrect ping_secret")
-    end
-
-    current_time = db_current_time
-    self.last_ping_at = current_time
-
-    @bypass_arvados_authorization = true
-
-    # Record IP address
-    if self.ip_address.nil?
-      logger.info "#{self.uuid} ip_address= #{o[:ip]}"
-      self.ip_address = o[:ip]
-      self.first_ping_at = current_time
-    end
-
-    # Record instance ID if not already known
-    if o[:ec2_instance_id]
-      if !self.info['ec2_instance_id']
-        self.info['ec2_instance_id'] = o[:ec2_instance_id]
-      elsif self.info['ec2_instance_id'] != o[:ec2_instance_id]
-        logger.debug "Multiple nodes have credentials for #{self.uuid}"
-        raise "#{self.uuid} is already running at #{self.info['ec2_instance_id']} so rejecting ping from #{o[:ec2_instance_id]}"
-      end
-    end
-
-    assign_slot
-
-    # Record other basic stats
-    ['total_cpu_cores', 'total_ram_mb', 'total_scratch_mb'].each do |key|
-      if value = (o[key] or o[key.to_sym])
-        self.properties[key] = value.to_i
-      else
-        self.properties.delete(key)
-      end
-    end
-
-    save!
-  end
-
-  def assign_slot
-    return if self.slot_number.andand > 0
-    while true
-      self.slot_number = self.class.available_slot_number
-      if self.slot_number.nil?
-        raise "No available node slots"
-      end
-      begin
-        save!
-        return assign_hostname
-      rescue ActiveRecord::RecordNotUnique
-        # try again
-      end
-    end
-  end
-
-  protected
-
-  def assign_hostname
-    if self.hostname.nil? and Rails.configuration.Containers.SLURM.Managed.AssignNodeHostname
-      self.hostname = self.class.hostname_for_slot(self.slot_number)
-    end
-  end
-
-  def self.available_slot_number
-    # Join the sequence 1..max with the nodes table. Return the first
-    # (i.e., smallest) value that doesn't match the slot_number of any
-    # existing node.
-    connection.exec_query('SELECT n FROM generate_series(1, $1) AS slot(n)
-                          LEFT JOIN nodes ON n=slot_number
-                          WHERE slot_number IS NULL
-                          LIMIT 1',
-                          # query label:
-                          'Node.available_slot_number',
-                          # bind vars:
-                          [MAX_VMS],
-                         ).rows.first.andand.first
-  end
-
-  def ensure_ping_secret
-    self.info['ping_secret'] ||= rand(2**256).to_s(36)
-  end
-
-  def dns_server_update
-    if saved_change_to_ip_address? && ip_address
-      Node.where('id != ? and ip_address = ?',
-                 id, ip_address).each do |stale_node|
-        # One or more(!) stale node records have the same IP address
-        # as the new node. Clear the ip_address field on the stale
-        # nodes. Otherwise, we (via SLURM) might inadvertently connect
-        # to the new node using the old node's hostname.
-        stale_node.update!(ip_address: nil)
-      end
-    end
-    if hostname_before_last_save && saved_change_to_hostname?
-      self.class.dns_server_update(hostname_before_last_save, UNUSED_NODE_IP)
-    end
-    if hostname && (saved_change_to_hostname? || saved_change_to_ip_address?)
-      self.class.dns_server_update(hostname, ip_address || UNUSED_NODE_IP)
-    end
-  end
-
-  def self.dns_server_update hostname, ip_address
-    ok = true
-
-    ptr_domain = ip_address.
-      split('.').reverse.join('.').concat('.in-addr.arpa')
-
-    template_vars = {
-      hostname: hostname,
-      uuid_prefix: Rails.configuration.ClusterID,
-      ip_address: ip_address,
-      ptr_domain: ptr_domain,
-    }
-
-    if (!Rails.configuration.Containers.SLURM.Managed.DNSServerConfDir.to_s.empty? and
-        !Rails.configuration.Containers.SLURM.Managed.DNSServerConfTemplate.to_s.empty?)
-      tmpfile = nil
-      begin
-        begin
-          template = IO.read(Rails.configuration.Containers.SLURM.Managed.DNSServerConfTemplate)
-        rescue IOError, SystemCallError => e
-          logger.error "Reading #{Rails.configuration.Containers.SLURM.Managed.DNSServerConfTemplate}: #{e.message}"
-          raise
-        end
-
-        hostfile = File.join Rails.configuration.Containers.SLURM.Managed.DNSServerConfDir, "#{hostname}.conf"
-        Tempfile.open(["#{hostname}-", ".conf.tmp"],
-                                 Rails.configuration.Containers.SLURM.Managed.DNSServerConfDir) do |f|
-          tmpfile = f.path
-          f.puts template % template_vars
-        end
-        File.rename tmpfile, hostfile
-      rescue IOError, SystemCallError => e
-        logger.error "Writing #{hostfile}: #{e.message}"
-        ok = false
-      ensure
-        if tmpfile and File.file? tmpfile
-          # Cleanup remaining temporary file.
-          File.unlink tmpfile
-        end
-      end
-    end
-
-    if !Rails.configuration.Containers.SLURM.Managed.DNSServerUpdateCommand.empty?
-      cmd = Rails.configuration.Containers.SLURM.Managed.DNSServerUpdateCommand % template_vars
-      if not system cmd
-        logger.error "dns_server_update_command #{cmd.inspect} failed: #{$?}"
-        ok = false
-      end
-    end
-
-    if (!Rails.configuration.Containers.SLURM.Managed.DNSServerConfDir.to_s.empty? and
-        !Rails.configuration.Containers.SLURM.Managed.DNSServerReloadCommand.to_s.empty?)
-      restartfile = File.join(Rails.configuration.Containers.SLURM.Managed.DNSServerConfDir, 'restart.txt')
-      begin
-        File.open(restartfile, 'w') do |f|
-          # Typically, this is used to trigger a dns server restart
-          f.puts Rails.configuration.Containers.SLURM.Managed.DNSServerReloadCommand
-        end
-      rescue IOError, SystemCallError => e
-        logger.error "Unable to write #{restartfile}: #{e.message}"
-        ok = false
-      end
-    end
-
-    ok
-  end
-
-  def self.hostname_for_slot(slot_number)
-    config = Rails.configuration.Containers.SLURM.Managed.AssignNodeHostname
-
-    return nil if !config
-
-    sprintf(config, {:slot_number => slot_number})
-  end
-
-  # At startup, make sure all DNS entries exist.  Otherwise, slurmctld
-  # will refuse to start.
-  if (!Rails.configuration.Containers.SLURM.Managed.DNSServerConfDir.to_s.empty? and
-      !Rails.configuration.Containers.SLURM.Managed.DNSServerConfTemplate.to_s.empty? and
-      !Rails.configuration.Containers.SLURM.Managed.AssignNodeHostname.empty?)
-
-    (0..MAX_VMS-1).each do |slot_number|
-      hostname = hostname_for_slot(slot_number)
-      hostfile = File.join Rails.configuration.Containers.SLURM.Managed.DNSServerConfDir, "#{hostname}.conf"
-      if !File.exist? hostfile
-        n = Node.where(:slot_number => slot_number).first
-        if n.nil? or n.ip_address.nil?
-          dns_server_update(hostname, UNUSED_NODE_IP)
-        else
-          dns_server_update(hostname, n.ip_address)
-        end
-      end
-    end
-  end
-
-  def permission_to_update
-    @bypass_arvados_authorization or super
-  end
-
-  def permission_to_create
-    current_user and current_user.is_admin
-  end
-end
diff --git a/services/api/app/models/pipeline_instance.rb b/services/api/app/models/pipeline_instance.rb
deleted file mode 100644
index 0b0af8b87d..0000000000
--- a/services/api/app/models/pipeline_instance.rb
+++ /dev/null
@@ -1,176 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-class PipelineInstance < ArvadosModel
-  include HasUuid
-  include KindAndEtag
-  include CommonApiTemplate
-  serialize :components, Hash
-  serialize :properties, Hash
-  serialize :components_summary, Hash
-  belongs_to :pipeline_template,
-             foreign_key: 'pipeline_template_uuid',
-             primary_key: 'uuid',
-             optional: true
-
-  before_validation :bootstrap_components
-  before_validation :update_state
-  before_validation :verify_status
-  before_validation :update_timestamps_when_state_changes
-  before_create :set_state_before_save
-  before_save :set_state_before_save
-  before_create :create_disabled
-  before_update :update_disabled
-
-  api_accessible :user, extend: :common do |t|
-    t.add :pipeline_template_uuid
-    t.add :name
-    t.add :components
-    t.add :properties
-    t.add :state
-    t.add :components_summary
-    t.add :description
-    t.add :started_at
-    t.add :finished_at
-  end
-
-  # Supported states for a pipeline instance
-  States =
-    [
-     (New = 'New'),
-     (Ready = 'Ready'),
-     (RunningOnServer = 'RunningOnServer'),
-     (RunningOnClient = 'RunningOnClient'),
-     (Paused = 'Paused'),
-     (Failed = 'Failed'),
-     (Complete = 'Complete'),
-    ]
-
-  def self.limit_index_columns_read
-    ["components"]
-  end
-
-  # if all components have input, the pipeline is Ready
-  def components_look_ready?
-    if !self.components || self.components.empty?
-      return false
-    end
-
-    all_components_have_input = true
-    self.components.each do |name, component|
-      component['script_parameters'].andand.each do |parametername, parameter|
-        parameter = { 'value' => parameter } unless parameter.is_a? Hash
-        if parameter['value'].nil? and parameter['required']
-          if parameter['output_of']
-            next
-          end
-          all_components_have_input = false
-          break
-        end
-      end
-    end
-    return all_components_have_input
-  end
-
-  def progress_table
-    begin
-      # v0 pipeline format
-      nrow = -1
-      components['steps'].collect do |step|
-        nrow += 1
-        row = [nrow, step['name']]
-        if step['complete'] and step['complete'] != 0
-          if step['output_data_locator']
-            row << 1.0
-          else
-            row << 0.0
-          end
-        else
-          row << 0.0
-          if step['failed']
-            self.state = Failed
-          end
-        end
-        row << (step['warehousejob']['id'] rescue nil)
-        row << (step['warehousejob']['revision'] rescue nil)
-        row << step['output_data_locator']
-        row << (Time.parse(step['warehousejob']['finishtime']) rescue nil)
-        row
-      end
-    rescue
-      []
-    end
-  end
-
-  def progress_ratio
-    t = progress_table
-    return 0 if t.size < 1
-    t.collect { |r| r[2] }.inject(0.0) { |sum,a| sum += a } / t.size
-  end
-
-  def self.queue
-    self.where("state = 'RunningOnServer'")
-  end
-
-  def cancel(cascade: false, need_transaction: true)
-    raise "No longer supported"
-  end
-
-  protected
-  def bootstrap_components
-    if pipeline_template and (!components or components.empty?)
-      self.components = pipeline_template.components.deep_dup
-    end
-  end
-
-  def update_state
-    if components and progress_ratio == 1.0
-      self.state = Complete
-    end
-  end
-
-  def verify_status
-    changed_attributes = self.changed
-
-    if new_record? or 'components'.in? changed_attributes
-      self.state ||= New
-      if (self.state == New) and self.components_look_ready?
-        self.state = Ready
-      end
-    end
-
-    if !self.state.in?(States)
-      errors.add :state, "'#{state.inspect} must be one of: [#{States.join ', '}]"
-      throw(:abort)
-    end
-  end
-
-  def set_state_before_save
-    if self.components_look_ready? && (!self.state || self.state == New)
-      self.state = Ready
-    end
-  end
-
-  def update_timestamps_when_state_changes
-    return if not (state_changed? or new_record?)
-
-    case state
-    when RunningOnServer, RunningOnClient
-      self.started_at ||= db_current_time
-    when Failed, Complete
-      current_time = db_current_time
-      self.started_at ||= current_time
-      self.finished_at ||= current_time
-    end
-  end
-
-
-  def create_disabled
-    raise "Disabled"
-  end
-
-  def update_disabled
-    raise "Disabled"
-  end
-end
diff --git a/services/api/app/models/pipeline_template.rb b/services/api/app/models/pipeline_template.rb
deleted file mode 100644
index 7c694698e0..0000000000
--- a/services/api/app/models/pipeline_template.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-class PipelineTemplate < ArvadosModel
-  before_create :create_disabled
-  before_update :update_disabled
-
-  include HasUuid
-  include KindAndEtag
-  include CommonApiTemplate
-  serialize :components, Hash
-
-  api_accessible :user, extend: :common do |t|
-    t.add :name
-    t.add :components
-    t.add :description
-  end
-
-  def self.limit_index_columns_read
-    ["components"]
-  end
-
-  def create_disabled
-    raise "Disabled"
-  end
-
-  def update_disabled
-    raise "Disabled"
-  end
-end
diff --git a/services/api/app/models/repository.rb b/services/api/app/models/repository.rb
deleted file mode 100644
index 46f2de6ee4..0000000000
--- a/services/api/app/models/repository.rb
+++ /dev/null
@@ -1,127 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-class Repository < ArvadosModel
-  include HasUuid
-  include KindAndEtag
-  include CommonApiTemplate
-
-  # Order is important here.  We must validate the owner before we can
-  # validate the name.
-  validate :valid_owner
-  validate :name_format, :if => Proc.new { |r| r.errors[:owner_uuid].empty? }
-  validates(:name, uniqueness: true, allow_nil: false)
-
-  api_accessible :user, extend: :common do |t|
-    t.add :name
-    t.add :fetch_url
-    t.add :push_url
-    t.add :clone_urls
-  end
-
-  def self.attributes_required_columns
-    super.merge("clone_urls" => ["name"],
-                "fetch_url" => ["name"],
-                "push_url" => ["name"])
-  end
-
-  # Deprecated. Use clone_urls instead.
-  def push_url
-    ssh_clone_url
-  end
-
-  # Deprecated. Use clone_urls instead.
-  def fetch_url
-    ssh_clone_url
-  end
-
-  def clone_urls
-    [ssh_clone_url, https_clone_url].compact
-  end
-
-  def server_path
-    # Find where the repository is stored on the API server's filesystem,
-    # and return that path, or nil if not found.
-    # This method is only for the API server's internal use, and should not
-    # be exposed through the public API.  Following our current gitolite
-    # setup, it searches for repositories stored by UUID, then name; and it
-    # prefers bare repositories over checkouts.
-    [["%s.git"], ["%s", ".git"]].each do |repo_base, *join_args|
-      [:uuid, :name].each do |path_attr|
-        git_dir = File.join(Rails.configuration.Git.Repositories,
-                            repo_base % send(path_attr), *join_args)
-        return git_dir if File.exist?(git_dir)
-      end
-    end
-    nil
-  end
-
-  protected
-
-  def permission_to_update
-    if not super
-      false
-    elsif current_user.is_admin
-      true
-    elsif name_changed?
-      current_user.uuid == owner_uuid
-    else
-      true
-    end
-  end
-
-  def owner
-    User.find_by_uuid(owner_uuid)
-  end
-
-  def valid_owner
-    if owner.nil? or (owner.username.nil? and (owner.uuid != system_user_uuid))
-      errors.add(:owner_uuid, "must refer to a user with a username")
-      false
-    end
-  end
-
-  def name_format
-    if owner.uuid == system_user_uuid
-      prefix_match = ""
-      errmsg_start = "must be"
-    else
-      prefix_match = Regexp.escape(owner.username + "/")
-      errmsg_start = "must be the owner's username, then '/', then"
-    end
-    if not (/^#{prefix_match}[A-Za-z][A-Za-z0-9]*$/.match(name))
-      errors.add(:name,
-                 "#{errmsg_start} a letter followed by alphanumerics, expected pattern '#{prefix_match}[A-Za-z][A-Za-z0-9]*' but was '#{name}'")
-      false
-    end
-  end
-
-  def ssh_clone_url
-    _clone_url Rails.configuration.Services.GitSSH.andand.ExternalURL, 'ssh://git@git.%s.arvadosapi.com'
-  end
-
-  def https_clone_url
-    _clone_url Rails.configuration.Services.GitHTTP.andand.ExternalURL, 'https://git.%s.arvadosapi.com/'
-  end
-
-  def _clone_url config_var, default_base_fmt
-    if not config_var
-      return ""
-    end
-    prefix = new_record? ? Rails.configuration.ClusterID : uuid[0,5]
-    if prefix == Rails.configuration.ClusterID and config_var != URI("")
-      base = config_var
-    else
-      base = URI(default_base_fmt % prefix)
-    end
-    if base.path == ""
-      base.path = "/"
-    end
-    if base.scheme == "ssh"
-      '%s@%s:%s.git' % [base.user, base.host, name]
-    else
-      '%s%s.git' % [base, name]
-    end
-  end
-end
diff --git a/services/api/app/models/specimen.rb b/services/api/app/models/specimen.rb
deleted file mode 100644
index 32d5ed57f3..0000000000
--- a/services/api/app/models/specimen.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-class Specimen < ArvadosModel
-  include HasUuid
-  include KindAndEtag
-  include CommonApiTemplate
-  serialize :properties, Hash
-
-  api_accessible :user, extend: :common do |t|
-    t.add :material
-    t.add :properties
-  end
-
-  def properties
-    @properties ||= Hash.new
-    super
-  end
-end
diff --git a/services/api/app/models/trait.rb b/services/api/app/models/trait.rb
deleted file mode 100644
index 2d3556b51d..0000000000
--- a/services/api/app/models/trait.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-class Trait < ArvadosModel
-  include HasUuid
-  include KindAndEtag
-  include CommonApiTemplate
-  serialize :properties, Hash
-
-  api_accessible :user, extend: :common do |t|
-    t.add :name
-    t.add :properties
-  end
-end
diff --git a/services/api/app/models/user.rb b/services/api/app/models/user.rb
index 5a95fb0b88..c104ac6fda 100644
--- a/services/api/app/models/user.rb
+++ b/services/api/app/models/user.rb
@@ -25,9 +25,6 @@ class User < ArvadosModel
   before_update :prevent_privilege_escalation
   before_update :prevent_inactive_admin
   before_update :prevent_nonadmin_system_root
-  before_update :verify_repositories_empty, :if => Proc.new {
-    username.nil? and username_changed?
-  }
   after_update :setup_on_activate
 
   before_create :check_auto_admin
@@ -49,16 +46,10 @@ class User < ArvadosModel
   before_update :before_ownership_change
   after_update :after_ownership_change
   after_update :send_profile_created_notification
-  after_update :sync_repository_names, :if => Proc.new {
-    (uuid != system_user_uuid) and
-    saved_change_to_username? and
-    (not username_before_last_save.nil?)
-  }
   before_destroy :clear_permissions
   after_destroy :remove_self_from_permissions
 
   has_many :authorized_keys, foreign_key: 'authorized_user_uuid', primary_key: 'uuid'
-  has_many :repositories, foreign_key: 'owner_uuid', primary_key: 'uuid'
 
   default_scope { where('redirect_to_user_uuid is null') }
 
@@ -261,7 +252,7 @@ SELECT target_uuid, perm_level
   end
 
   # create links
-  def setup(repo_name: nil, vm_uuid: nil, send_notification_email: nil)
+  def setup(vm_uuid: nil, send_notification_email: nil)
     newly_invited = Link.where(tail_uuid: self.uuid,
                               head_uuid: all_users_group_uuid,
                               link_class: 'permission').empty?
@@ -271,12 +262,6 @@ SELECT target_uuid, perm_level
     # direction which makes this user visible to other users.
     group_perms = add_to_all_users_group
 
-    # Add git repo
-    repo_perm = if (!repo_name.nil? || Rails.configuration.Users.AutoSetupNewUsersWithRepository) and !username.nil?
-                  repo_name ||= "#{username}/#{username}"
-                  create_user_repo_link repo_name
-                end
-
     # Add virtual machine
     if vm_uuid.nil? and !Rails.configuration.Users.AutoSetupNewUsersWithVmUUID.empty?
       vm_uuid = Rails.configuration.Users.AutoSetupNewUsersWithVmUUID
@@ -301,10 +286,10 @@ SELECT target_uuid, perm_level
 
     forget_cached_group_perms
 
-    return [repo_perm, vm_login_perm, *group_perms, self].compact
+    return [vm_login_perm, *group_perms, self].compact
   end
 
-  # delete user signatures, login, repo, and vm perms, and mark as inactive
+  # delete user signatures, login, and vm perms, and mark as inactive
   def unsetup
     if self.uuid == system_user_uuid
       raise "System root user cannot be deactivated"
@@ -483,30 +468,13 @@ SELECT target_uuid, perm_level
         klass.where(column => uuid).update_all(column => new_user.uuid)
       end
 
-      # Need to update repository names to new username
-      if username
-        old_repo_name_re = /^#{Regexp.escape(username)}\//
-        Repository.where(:owner_uuid => uuid).each do |repo|
-          repo.owner_uuid = new_user.uuid
-          repo_name_sub = "#{new_user.username}/"
-          name = repo.name.sub(old_repo_name_re, repo_name_sub)
-          while (conflict = Repository.where(:name => name).first) != nil
-            repo_name_sub += "migrated"
-            name = repo.name.sub(old_repo_name_re, repo_name_sub)
-          end
-          repo.name = name
-          repo.save!
-        end
-      end
-
       # References to the merged user's "home project" are updated to
       # point to new_owner_uuid.
       ActiveRecord::Base.descendants.reject(&:abstract_class?).each do |klass|
         next if [ApiClientAuthorization,
                  AuthorizedKey,
                  Link,
-                 Log,
-                 Repository].include?(klass)
+                 Log].include?(klass)
         next if !klass.columns.collect(&:name).include?('owner_uuid')
         klass.where(owner_uuid: uuid).update_all(owner_uuid: new_owner_uuid)
       end
@@ -889,24 +857,8 @@ SELECT target_uuid, perm_level
     merged
   end
 
-  def create_user_repo_link(repo_name)
-    # repo_name is optional
-    if not repo_name
-      logger.warn ("Repository name not given for #{self.uuid}.")
-      return
-    end
-
-    repo = Repository.where(owner_uuid: uuid, name: repo_name).first_or_create!
-    logger.info { "repo uuid: " + repo[:uuid] }
-    repo_perm = Link.where(tail_uuid: uuid, head_uuid: repo.uuid,
-                           link_class: "permission",
-                           name: "can_manage").first_or_create!
-    logger.info { "repo permission: " + repo_perm[:uuid] }
-    return repo_perm
-  end
-
   # create login permission for the given vm_uuid, if it does not already exist
-  def create_vm_login_permission_link(vm_uuid, repo_name)
+  def create_vm_login_permission_link(vm_uuid, username)
     # vm uuid is optional
     return if vm_uuid == ""
 
@@ -924,11 +876,11 @@ SELECT target_uuid, perm_level
 
     login_perm = Link.
       where(login_attrs).
-      select { |link| link.properties["username"] == repo_name }.
+      select { |link| link.properties["username"] == username }.
       first
 
     login_perm ||= Link.
-      create(login_attrs.merge(properties: {"username" => repo_name}))
+      create(login_attrs.merge(properties: {"username" => username}))
 
     logger.info { "login permission: " + login_perm[:uuid] }
     login_perm
@@ -1001,22 +953,6 @@ SELECT target_uuid, perm_level
     end
   end
 
-  def verify_repositories_empty
-    unless repositories.first.nil?
-      errors.add(:username, "can't be unset when the user owns repositories")
-      throw(:abort)
-    end
-  end
-
-  def sync_repository_names
-    old_name_re = /^#{Regexp.escape(username_before_last_save)}\//
-    name_sub = "#{username}/"
-    repositories.find_each do |repo|
-      repo.name = repo.name.sub(old_name_re, name_sub)
-      repo.save!
-    end
-  end
-
   def identity_url_nil_if_empty
     if identity_url == ""
       self.identity_url = nil
diff --git a/services/api/config/arvados_config.rb b/services/api/config/arvados_config.rb
index f8b9ff8ecd..594e247a36 100644
--- a/services/api/config/arvados_config.rb
+++ b/services/api/config/arvados_config.rb
@@ -142,15 +142,6 @@ arvcfg.declare_config "Containers.Logging.LogPartialLineThrottlePeriod", ActiveS
 arvcfg.declare_config "Containers.Logging.LogUpdatePeriod", ActiveSupport::Duration, :crunch_log_update_period
 arvcfg.declare_config "Containers.Logging.LogUpdateSize", Integer, :crunch_log_update_size
 arvcfg.declare_config "Containers.Logging.MaxAge", ActiveSupport::Duration, :clean_container_log_rows_after
-arvcfg.declare_config "Containers.SLURM.Managed.DNSServerConfDir", Pathname, :dns_server_conf_dir
-arvcfg.declare_config "Containers.SLURM.Managed.DNSServerConfTemplate", Pathname, :dns_server_conf_template
-arvcfg.declare_config "Containers.SLURM.Managed.DNSServerReloadCommand", String, :dns_server_reload_command
-arvcfg.declare_config "Containers.SLURM.Managed.DNSServerUpdateCommand", String, :dns_server_update_command
-arvcfg.declare_config "Containers.SLURM.Managed.ComputeNodeDomain", String, :compute_node_domain
-arvcfg.declare_config "Containers.SLURM.Managed.ComputeNodeNameservers", Hash, :compute_node_nameservers, ->(cfg, k, v) { arrayToHash cfg, "Containers.SLURM.Managed.ComputeNodeNameservers", v }
-arvcfg.declare_config "Containers.SLURM.Managed.AssignNodeHostname", String, :assign_node_hostname
-arvcfg.declare_config "Containers.JobsAPI.Enable", String, :enable_legacy_jobs_api, ->(cfg, k, v) { ConfigLoader.set_cfg cfg, "Containers.JobsAPI.Enable", v.to_s }
-arvcfg.declare_config "Containers.JobsAPI.GitInternalDir", String, :git_internal_dir
 arvcfg.declare_config "Mail.MailchimpAPIKey", String, :mailchimp_api_key
 arvcfg.declare_config "Mail.MailchimpListID", String, :mailchimp_list_id
 arvcfg.declare_config "Services.Controller.ExternalURL", URI
diff --git a/services/api/config/routes.rb b/services/api/config/routes.rb
index b87e86f664..df3c057b57 100644
--- a/services/api/config/routes.rb
+++ b/services/api/config/routes.rb
@@ -34,8 +34,6 @@ Rails.application.routes.draw do
         post 'trash', on: :member
         post 'untrash', on: :member
       end
-      resources :humans
-      resources :job_tasks
       resources :containers do
         get 'auth', on: :member
         post 'lock', on: :member
@@ -47,33 +45,12 @@ Rails.application.routes.draw do
       resources :container_requests do
         get 'container_status', on: :member
       end
-      resources :jobs do
-        get 'queue', on: :collection
-        get 'queue_size', on: :collection
-        post 'cancel', on: :member
-        post 'lock', on: :member
-      end
-      resources :keep_disks do
-        post 'ping', on: :collection
-      end
       resources :keep_services do
         get 'accessible', on: :collection
       end
       resources :links
       resources :logs
-      resources :nodes do
-        post 'ping', on: :member
-      end
-      resources :pipeline_instances do
-        post 'cancel', on: :member
-      end
-      resources :pipeline_templates
       resources :workflows
-      resources :repositories do
-        get 'get_all_permissions', on: :collection
-      end
-      resources :specimens
-      resources :traits
       resources :user_agreements do
         get 'signatures', on: :collection
         post 'sign', on: :collection
diff --git a/services/api/lib/can_be_an_owner.rb b/services/api/lib/can_be_an_owner.rb
index e09037819c..995f6f334c 100644
--- a/services/api/lib/can_be_an_owner.rb
+++ b/services/api/lib/can_be_an_owner.rb
@@ -14,11 +14,23 @@ module CanBeAnOwner
     # record when other objects refer to it.
     ActiveRecord::Base.connection.tables.each do |t|
       next if t == base.table_name
-      next if t == 'schema_migrations'
-      next if t == 'permission_refresh_lock'
-      next if t == 'ar_internal_metadata'
-      next if t == 'commit_ancestors'
-      next if t == 'commits'
+      next if t.in?([
+                      'schema_migrations',
+                      'permission_refresh_lock',
+                      'ar_internal_metadata',
+                      'commit_ancestors',
+                      'commits',
+                      'humans',
+                      'jobs',
+                      'job_tasks',
+                      'keep_disks',
+                      'nodes',
+                      'pipeline_instances',
+                      'pipeline_templates',
+                      'repositories',
+                      'specimens',
+                      'traits',
+                    ])
       klass = t.classify.constantize
       next unless klass and 'owner_uuid'.in?(klass.columns.collect(&:name))
       base.has_many(t.to_sym,
diff --git a/services/api/test/fixtures/api_client_authorizations.yml b/services/api/test/fixtures/api_client_authorizations.yml
index c6ade21f8b..882937034b 100644
--- a/services/api/test/fixtures/api_client_authorizations.yml
+++ b/services/api/test/fixtures/api_client_authorizations.yml
@@ -128,14 +128,6 @@ active_userlist:
   expires_at: 2038-01-01 00:00:00
   scopes: ["GET /arvados/v1/users"]
 
-active_specimens:
-  uuid: zzzzz-gj3su-177z32aux8dg2s1
-  api_client: untrusted
-  user: active
-  api_token: activespecimensabcdefghijklmnopqrstuvwxyz123456890
-  expires_at: 2038-01-01 00:00:00
-  scopes: ["GET /arvados/v1/specimens/"]
-
 active_apitokens:
   uuid: zzzzz-gj3su-187z32aux8dg2s1
   api_client: trusted_workbench
@@ -160,14 +152,21 @@ spectator:
   api_token: zw2f4gwx8hw8cjre7yp6v1zylhrhn3m5gvjq73rtpwhmknrybu
   expires_at: 2038-01-01 00:00:00
 
-spectator_specimens:
+foo:
+  uuid: zzzzz-gj3su-fohzae5ib1aseiv
+  api_client: untrusted
+  user: user_foo_in_sharing_group
+  api_token: lokah4xip8ahgee8oof5zitah3ohdai6je9cu1uogh4bai3ohw
+  expires_at: 2038-01-01 00:00:00
+
+foo_collections:
   uuid: zzzzz-gj3su-217z32aux8dg2s1
   api_client: untrusted
-  user: spectator
-  api_token: spectatorspecimensabcdefghijklmnopqrstuvwxyz123245
+  user: user_foo_in_sharing_group
+  api_token: spectatorcollectionscdefghijklmnopqrstuvwxyz123245
   expires_at: 2038-01-01 00:00:00
-  scopes: ["GET /arvados/v1/specimens", "GET /arvados/v1/specimens/",
-           "POST /arvados/v1/specimens"]
+  scopes: ["GET /arvados/v1/collections", "GET /arvados/v1/collections/",
+           "POST /arvados/v1/collections"]
 
 inactive:
   uuid: zzzzz-gj3su-227z32aux8dg2s1
diff --git a/services/api/test/fixtures/collections.yml b/services/api/test/fixtures/collections.yml
index 72aad1d68e..a193150e29 100644
--- a/services/api/test/fixtures/collections.yml
+++ b/services/api/test/fixtures/collections.yml
@@ -451,22 +451,6 @@ unique_expired_collection2:
   manifest_text: ". 29d7797f1888013986899bc9083783fa+3 0:3:expired2\n"
   name: unique_expired_collection2
 
-# a collection with a log file that can be parsed by the log viewer
-# This collection hash matches the following log text:
-#    2014-01-01_12:00:01 zzzzz-8i9sb-abcdefghijklmno 0  log message 1
-#    2014-01-01_12:00:02 zzzzz-8i9sb-abcdefghijklmno 0  log message 2
-#    2014-01-01_12:00:03 zzzzz-8i9sb-abcdefghijklmno 0  log message 3
-#
-real_log_collection:
-  uuid: zzzzz-4zz18-op4e2lbej01tcvu
-  current_version_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
-
 collection_in_home_project_with_same_name_as_in_aproject:
   uuid: zzzzz-4zz18-12342x4u7ftabcd
   current_version_uuid: zzzzz-4zz18-12342x4u7ftabcd
@@ -1076,6 +1060,18 @@ collection_with_uri_prop:
   properties:
     "http://schema.org/example": "value1"
 
+container_log_collection:
+  uuid: zzzzz-4zz18-logcollection00
+  current_version_uuid: zzzzz-4zz18-logcollection00
+  portable_data_hash: b1e66f713c04d28ddbaced89096f4838+210
+  owner_uuid: zzzzz-tpzed-000000000000000
+  created_at: 2020-10-29T00:51:44.075594000Z
+  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
+  modified_by_user_uuid: zzzzz-tpzed-d9tiejq69daie8f
+  modified_at: 2020-10-29T00:51:44.072109000Z
+  manifest_text: ". 8c12f5f5297b7337598170c6f531fcee+7882 0:0:arv-mount.txt 0:1910:container.json 1910:1264:crunch-run.txt 3174:1005:crunchstat.txt 4179:659:hoststat.txt 4838:2811:node-info.txt 7649:233:node.json 0:0:stderr.txt\n"
+  name: a real log collection for a completed container
+
 log_collection:
   uuid: zzzzz-4zz18-logcollection01
   current_version_uuid: zzzzz-4zz18-logcollection01
@@ -1086,7 +1082,7 @@ log_collection:
   modified_by_user_uuid: zzzzz-tpzed-d9tiejq69daie8f
   modified_at: 2020-10-29T00:51:44.072109000Z
   manifest_text: ". 8c12f5f5297b7337598170c6f531fcee+7882 0:0:arv-mount.txt 0:1910:container.json 1910:1264:crunch-run.txt 3174:1005:crunchstat.txt 4179:659:hoststat.txt 4838:2811:node-info.txt 7649:233:node.json 0:0:stderr.txt\n./log\\040for\\040container\\040ce8i5-dz642-h4kd64itncdcz8l 8c12f5f5297b7337598170c6f531fcee+7882 0:0:arv-mount.txt 0:1910:container.json 1910:1264:crunch-run.txt 3174:1005:crunchstat.txt 4179:659:hoststat.txt 4838:2811:node-info.txt 7649:233:node.json 0:0:stderr.txt\n"
-  name: a real log collection for a completed container
+  name: a real log collection for a completed container request
 
 log_collection2:
   uuid: zzzzz-4zz18-logcollection02
diff --git a/services/api/test/fixtures/container_requests.yml b/services/api/test/fixtures/container_requests.yml
index 71c7a54df3..3b035416cb 100644
--- a/services/api/test/fixtures/container_requests.yml
+++ b/services/api/test/fixtures/container_requests.yml
@@ -1056,6 +1056,36 @@ runtime_token:
     ram: 123
   mounts: {}
 
+read_foo_write_bar:
+  uuid: zzzzz-xvdhp-readfoowritebar
+  owner_uuid: zzzzz-tpzed-000000000000000
+  state: Final
+  created_at: 2024-01-11 11:11:11.111111111 Z
+  updated_at: 2024-01-11 11:11:11.111111111 Z
+  modified_at: 2024-01-11 11:11:11.111111111 Z
+  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
+  container_image: test
+  cwd: /
+  mounts:
+    stdin:
+      kind: collection
+      portable_data_hash: 1f4b0bc7583c2a7f9102c395f4ffc5e3+45
+      path: /foo
+    stdout:
+      kind: file
+      path: /mnt/out/bar
+    /mnt/out:
+      kind: tmp
+      capacity: 1000
+  container_uuid: zzzzz-dz642-readfoowritebar
+  log_uuid: zzzzz-4zz18-logcollection01
+  output_uuid: zzzzz-4zz18-ehbhgtheo8909or
+  output_path: test
+  command: ["echo", "-n", "bar"]
+  runtime_constraints:
+    ram: 10000000
+    vcpus: 1
+
 
 # Test Helper trims the rest of the file
 
diff --git a/services/api/test/fixtures/containers.yml b/services/api/test/fixtures/containers.yml
index 46bc1e50f9..8e40554a9b 100644
--- a/services/api/test/fixtures/containers.yml
+++ b/services/api/test/fixtures/containers.yml
@@ -485,3 +485,36 @@ cuda_container:
       device_count: 1
   secret_mounts: {}
   secret_mounts_md5: 99914b932bd37a50b983c5e7c90ae93b
+
+read_foo_write_bar:
+  uuid: zzzzz-dz642-readfoowritebar
+  owner_uuid: zzzzz-tpzed-000000000000000
+  state: Complete
+  exit_code: 0
+  priority: 1
+  created_at: 2024-01-11 11:11:11.111111111 Z
+  updated_at: 2024-01-11 11:11:11.111111111 Z
+  started_at: 2024-01-11 11:11:11.111111111 Z
+  finished_at: 2024-01-12 11:12:13.111111111 Z
+  container_image: test
+  cwd: /
+  mounts:
+    stdin:
+      kind: collection
+      portable_data_hash: 1f4b0bc7583c2a7f9102c395f4ffc5e3+45
+      path: /foo
+    stdout:
+      kind: file
+      path: /mnt/out/bar
+    /mnt/out:
+      kind: tmp
+      capacity: 1000
+  log: ea10d51bcf88862dbcc36eb292017dfd+45
+  output: fa7aeb5140e2848d39b416daeef4ffc5+45
+  output_path: test
+  command: ["echo", "-n", "bar"]
+  runtime_constraints:
+    ram: 10000000
+    vcpus: 1
+  secret_mounts: {}
+  secret_mounts_md5: 99914b932bd37a50b983c5e7c90ae93b
diff --git a/services/api/test/fixtures/humans.yml b/services/api/test/fixtures/humans.yml
deleted file mode 100644
index eee61efefe..0000000000
--- a/services/api/test/fixtures/humans.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-# File exists to ensure the table gets cleared during DatabaseController#reset
diff --git a/services/api/test/fixtures/job_tasks.yml b/services/api/test/fixtures/job_tasks.yml
deleted file mode 100644
index 6a857a02f2..0000000000
--- a/services/api/test/fixtures/job_tasks.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-running_job_task_1:
-  uuid: zzzzz-ot0gb-runningjobtask1
-  owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
-  created_at: <%= 3.minute.ago.to_fs(:db) %>
-  job_uuid: zzzzz-8i9sb-with2components
-
-running_job_task_2:
-  uuid: zzzzz-ot0gb-runningjobtask2
-  owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
-  created_at: <%= 3.minute.ago.to_fs(:db) %>
-  job_uuid: zzzzz-8i9sb-with2components
diff --git a/services/api/test/fixtures/jobs.yml b/services/api/test/fixtures/jobs.yml
deleted file mode 100644
index 54b38259ba..0000000000
--- a/services/api/test/fixtures/jobs.yml
+++ /dev/null
@@ -1,768 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-running:
-  uuid: zzzzz-8i9sb-pshmckwoma9plh7
-  owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
-  cancelled_at: ~
-  cancelled_by_user_uuid: ~
-  cancelled_by_client_uuid: ~
-  created_at: <%= 2.7.minute.ago.to_fs(:db) %>
-  started_at: <%= 2.7.minute.ago.to_fs(:db) %>
-  finished_at: ~
-  script: hash
-  repository: active/foo
-  script_version: 1de84a854e2b440dc53bf42f8548afa4c17da332
-  running: true
-  success: ~
-  output: ~
-  priority: 0
-  log: ~
-  is_locked_by_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  tasks_summary:
-    failed: 0
-    todo: 3
-    running: 1
-    done: 1
-  runtime_constraints: {}
-  state: Running
-  script_parameters_digest: 99914b932bd37a50b983c5e7c90ae93b
-
-running_cancelled:
-  uuid: zzzzz-8i9sb-4cf0nhn6xte809j
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  cancelled_at: <%= 1.minute.ago.to_fs(:db) %>
-  cancelled_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  cancelled_by_client_uuid: zzzzz-ozdt8-obw7foaks3qjyej
-  created_at: <%= 4.minute.ago.to_fs(:db) %>
-  started_at: <%= 3.minute.ago.to_fs(:db) %>
-  finished_at: ~
-  script: hash
-  repository: active/foo
-  script_version: 1de84a854e2b440dc53bf42f8548afa4c17da332
-  running: true
-  success: ~
-  output: ~
-  priority: 0
-  log: ~
-  is_locked_by_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  tasks_summary:
-    failed: 0
-    todo: 3
-    running: 1
-    done: 1
-  runtime_constraints: {}
-  state: Cancelled
-  script_parameters_digest: 99914b932bd37a50b983c5e7c90ae93b
-
-uses_nonexistent_script_version:
-  uuid: zzzzz-8i9sb-7m339pu0x9mla88
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  cancelled_at: ~
-  cancelled_by_user_uuid: ~
-  cancelled_by_client_uuid: ~
-  script_version: 7def43a4d3f20789dda4700f703b5514cc3ed250
-  created_at: <%= 5.minute.ago.to_fs(:db) %>
-  started_at: <%= 3.minute.ago.to_fs(:db) %>
-  finished_at: <%= 2.minute.ago.to_fs(:db) %>
-  script: hash
-  repository: active/foo
-  running: false
-  success: true
-  output: d41d8cd98f00b204e9800998ecf8427e+0
-  priority: 0
-  log: d41d8cd98f00b204e9800998ecf8427e+0
-  is_locked_by_uuid: ~
-  tasks_summary:
-    failed: 0
-    todo: 0
-    running: 0
-    done: 1
-  runtime_constraints: {}
-  state: Complete
-  script_parameters_digest: 99914b932bd37a50b983c5e7c90ae93b
-
-foobar:
-  uuid: zzzzz-8i9sb-aceg2bnq7jt7kon
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  cancelled_at: ~
-  cancelled_by_user_uuid: ~
-  cancelled_by_client_uuid: ~
-  script: hash
-  repository: active/foo
-  script_version: 7def43a4d3f20789dda4700f703b5514cc3ed250
-  script_parameters:
-    input: 1f4b0bc7583c2a7f9102c395f4ffc5e3+45
-  created_at: <%= 4.minute.ago.to_fs(:db) %>
-  started_at: <%= 3.minute.ago.to_fs(:db) %>
-  finished_at: <%= 2.minute.ago.to_fs(:db) %>
-  running: false
-  success: true
-  output: fa7aeb5140e2848d39b416daeef4ffc5+45
-  priority: 0
-  log: ea10d51bcf88862dbcc36eb292017dfd+45
-  is_locked_by_uuid: ~
-  tasks_summary:
-    failed: 0
-    todo: 0
-    running: 0
-    done: 1
-  runtime_constraints: {}
-  state: Complete
-  script_parameters_digest: 03a43a7d84f7fb022467b876c2950acd
-
-barbaz:
-  uuid: zzzzz-8i9sb-cjs4pklxxjykyuq
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  cancelled_at: ~
-  cancelled_by_user_uuid: ~
-  cancelled_by_client_uuid: ~
-  script_version: 7def43a4d3f20789dda4700f703b5514cc3ed250
-  script_parameters:
-    input: fa7aeb5140e2848d39b416daeef4ffc5+45
-    an_integer: 1
-  created_at: <%= 4.minute.ago.to_fs(:db) %>
-  started_at: <%= 3.minute.ago.to_fs(:db) %>
-  finished_at: <%= 2.minute.ago.to_fs(:db) %>
-  running: false
-  success: true
-  repository: active/foo
-  output: ea10d51bcf88862dbcc36eb292017dfd+45
-  priority: 0
-  log: d41d8cd98f00b204e9800998ecf8427e+0
-  is_locked_by_uuid: ~
-  tasks_summary:
-    failed: 0
-    todo: 0
-    running: 0
-    done: 1
-  runtime_constraints: {}
-  state: Complete
-  script_parameters_digest: c3d19d3ec50ac0914baa56b149640f73
-
-runningbarbaz:
-  uuid: zzzzz-8i9sb-cjs4pklxxjykyuj
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  cancelled_at: ~
-  cancelled_by_user_uuid: ~
-  cancelled_by_client_uuid: ~
-  script_version: 7def43a4d3f20789dda4700f703b5514cc3ed250
-  script_parameters:
-    input: fa7aeb5140e2848d39b416daeef4ffc5+45
-    an_integer: 1
-  created_at: <%= 4.minute.ago.to_fs(:db) %>
-  started_at: <%= 3.minute.ago.to_fs(:db) %>
-  finished_at: <%= 2.minute.ago.to_fs(:db) %>
-  running: true
-  success: ~
-  repository: active/foo
-  output: ea10d51bcf88862dbcc36eb292017dfd+45
-  priority: 0
-  log: d41d8cd98f00b204e9800998ecf8427e+0
-  is_locked_by_uuid: ~
-  tasks_summary:
-    failed: 0
-    todo: 0
-    running: 1
-    done: 0
-  runtime_constraints: {}
-  state: Running
-  script_parameters_digest: c3d19d3ec50ac0914baa56b149640f73
-
-previous_job_run:
-  uuid: zzzzz-8i9sb-cjs4pklxxjykqqq
-  created_at: <%= 14.minute.ago.to_fs(:db) %>
-  finished_at: <%= 13.minutes.ago.to_fs(:db) %>
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  repository: active/foo
-  script: hash
-  script_version: 4fe459abe02d9b365932b8f5dc419439ab4e2577
-  script_parameters:
-    input: fa7aeb5140e2848d39b416daeef4ffc5+45
-    an_integer: "1"
-  success: true
-  log: d41d8cd98f00b204e9800998ecf8427e+0
-  output: ea10d51bcf88862dbcc36eb292017dfd+45
-  state: Complete
-  script_parameters_digest: a5f03bbfb8ba88a2efe4a7852671605b
-
-previous_job_run_nil_log:
-  uuid: zzzzz-8i9sb-cjs4pklxxjykqq3
-  created_at: <%= 14.minute.ago.to_fs(:db) %>
-  finished_at: <%= 13.minutes.ago.to_fs(:db) %>
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  repository: active/foo
-  script: hash
-  script_version: 4fe459abe02d9b365932b8f5dc419439ab4e2577
-  script_parameters:
-    input: fa7aeb5140e2848d39b416daeef4ffc5+45
-    an_integer: "3"
-  success: true
-  log: ~
-  output: ea10d51bcf88862dbcc36eb292017dfd+45
-  state: Complete
-  script_parameters_digest: 445702df4029b8a6e7075b451ff1256a
-
-previous_ancient_job_run:
-  uuid: zzzzz-8i9sb-ahd7cie8jah9qui
-  created_at: <%= 366.days.ago.to_fs(:db) %>
-  finished_at: <%= 365.days.ago.to_fs(:db) %>
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  repository: active/foo
-  script: hash
-  script_version: 4fe459abe02d9b365932b8f5dc419439ab4e2577
-  script_parameters:
-    input: fa7aeb5140e2848d39b416daeef4ffc5+45
-    an_integer: "2"
-  success: true
-  log: d41d8cd98f00b204e9800998ecf8427e+0
-  output: ea10d51bcf88862dbcc36eb292017dfd+45
-  state: Complete
-  script_parameters_digest: 174dd339d44f2b259fadbab7ebdb8df9
-
-previous_docker_job_run:
-  uuid: zzzzz-8i9sb-k6emstgk4kw4yhi
-  created_at: <%= 14.minute.ago.to_fs(:db) %>
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  repository: active/foo
-  script: hash
-  script_version: 4fe459abe02d9b365932b8f5dc419439ab4e2577
-  script_parameters:
-    input: fa7aeb5140e2848d39b416daeef4ffc5+45
-    an_integer: "1"
-  runtime_constraints:
-    docker_image: arvados/apitestfixture
-  success: true
-  output: ea10d51bcf88862dbcc36eb292017dfd+45
-  docker_image_locator: fa3c1a9cb6783f85f2ecda037e07b8c3+167
-  state: Complete
-  script_parameters_digest: a5f03bbfb8ba88a2efe4a7852671605b
-  log: ea10d51bcf88862dbcc36eb292017dfd+45
-
-previous_ancient_docker_image_job_run:
-  uuid: zzzzz-8i9sb-t3b460aolxxuldl
-  created_at: <%= 144.minute.ago.to_fs(:db) %>
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  repository: active/foo
-  script: hash
-  script_version: 4fe459abe02d9b365932b8f5dc419439ab4e2577
-  script_parameters:
-    input: fa7aeb5140e2848d39b416daeef4ffc5+45
-    an_integer: "2"
-  runtime_constraints:
-    docker_image: arvados/apitestfixture
-  success: true
-  output: ea10d51bcf88862dbcc36eb292017dfd+45
-  docker_image_locator: b519d9cb706a29fc7ea24dbea2f05851+93
-  state: Complete
-  script_parameters_digest: 174dd339d44f2b259fadbab7ebdb8df9
-
-previous_job_run_with_arvados_sdk_version:
-  uuid: zzzzz-8i9sb-eoo0321or2dw2jg
-  created_at: <%= 14.minute.ago.to_fs(:db) %>
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  repository: active/foo
-  script: hash
-  script_version: 31ce37fe365b3dc204300a3e4c396ad333ed0556
-  script_parameters:
-    input: fa7aeb5140e2848d39b416daeef4ffc5+45
-    an_integer: "1"
-  runtime_constraints:
-    arvados_sdk_version: commit2
-    docker_image: arvados/apitestfixture
-  arvados_sdk_version: 00634b2b8a492d6f121e3cf1d6587b821136a9a7
-  docker_image_locator: fa3c1a9cb6783f85f2ecda037e07b8c3+167
-  success: true
-  output: ea10d51bcf88862dbcc36eb292017dfd+45
-  state: Complete
-  script_parameters_digest: a5f03bbfb8ba88a2efe4a7852671605b
-  log: ea10d51bcf88862dbcc36eb292017dfd+45
-
-previous_job_run_no_output:
-  uuid: zzzzz-8i9sb-cjs4pklxxjykppp
-  created_at: <%= 14.minute.ago.to_fs(:db) %>
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  repository: active/foo
-  script: hash
-  script_version: 4fe459abe02d9b365932b8f5dc419439ab4e2577
-  script_parameters:
-    input: fa7aeb5140e2848d39b416daeef4ffc5+45
-    an_integer: "2"
-  success: true
-  output: ~
-  state: Complete
-  script_parameters_digest: 174dd339d44f2b259fadbab7ebdb8df9
-
-previous_job_run_superseded_by_hash_branch:
-  # This supplied_script_version is a branch name with later commits.
-  uuid: zzzzz-8i9sb-aeviezu5dahph3e
-  created_at: <%= 15.minute.ago.to_fs(:db) %>
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  repository: active/shabranchnames
-  script: testscript
-  script_version: 7387838c69a21827834586cc42b467ff6c63293b
-  supplied_script_version: 738783
-  script_parameters: {}
-  success: true
-  output: d41d8cd98f00b204e9800998ecf8427e+0
-  state: Complete
-  script_parameters_digest: 99914b932bd37a50b983c5e7c90ae93b
-
-nondeterminisic_job_run:
-  uuid: zzzzz-8i9sb-cjs4pklxxjykyyy
-  created_at: <%= 14.minute.ago.to_fs(:db) %>
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  repository: active/foo
-  script: hash2
-  script_version: 4fe459abe02d9b365932b8f5dc419439ab4e2577
-  script_parameters:
-    input: fa7aeb5140e2848d39b416daeef4ffc5+45
-    an_integer: "1"
-  success: true
-  nondeterministic: true
-  state: Complete
-  script_parameters_digest: a5f03bbfb8ba88a2efe4a7852671605b
-
-nearly_finished_job:
-  uuid: zzzzz-8i9sb-2gx6rz0pjl033w3
-  created_at: <%= 14.minute.ago.to_fs(:db) %>
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  repository: arvados
-  script: doesnotexist
-  script_version: 309e25a64fe994867db8459543af372f850e25b9
-  script_parameters:
-    input: b519d9cb706a29fc7ea24dbea2f05851+249025
-  started_at: <%= 3.minute.ago.to_fs(:db) %>
-  finished_at: ~
-  running: true
-  success: ~
-  tasks_summary:
-    failed: 0
-    todo: 0
-    running: 1
-    done: 0
-  runtime_constraints: {}
-  state: Complete
-  script_parameters_digest: 7ea26d58a79b7f5db9f90fb1e33d3006
-
-queued:
-  uuid: zzzzz-8i9sb-grx15v5mjnsyxk7
-  created_at: <%= 1.minute.ago.to_fs(:db) %>
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  cancelled_at: ~
-  cancelled_by_user_uuid: ~
-  cancelled_by_client_uuid: ~
-  started_at: ~
-  finished_at: ~
-  script: foo
-  script_version: 1de84a854e2b440dc53bf42f8548afa4c17da332
-  script_parameters: {}
-  running: ~
-  success: ~
-  output: ~
-  priority: 0
-  log: ~
-  is_locked_by_uuid: ~
-  tasks_summary: {}
-  runtime_constraints: {}
-  state: Queued
-  script_parameters_digest: 99914b932bd37a50b983c5e7c90ae93b
-
-# A job with a log collection that can be parsed by the log viewer.
-job_with_real_log:
-  uuid: zzzzz-8i9sb-0vsrcqi7whchuil
-  created_at: 2014-09-01 12:00:00
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  log: 0b9a7787660e1fce4a93f33e01376ba6+81
-  script_version: 7def43a4d3f20789dda4700f703b5514cc3ed250
-  state: Complete
-  script_parameters_digest: 99914b932bd37a50b983c5e7c90ae93b
-
-cancelled:
-  uuid: zzzzz-8i9sb-4cf0abc123e809j
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  cancelled_at: <%= 1.minute.ago.to_fs(:db) %>
-  cancelled_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  cancelled_by_client_uuid: zzzzz-ozdt8-obw7foaks3qjyej
-  created_at: <%= 4.minute.ago.to_fs(:db) %>
-  started_at: <%= 3.minute.ago.to_fs(:db) %>
-  finished_at: ~
-  script_version: 1de84a854e2b440dc53bf42f8548afa4c17da332
-  running: false
-  success: ~
-  output: ~
-  priority: 0
-  log: ~
-  is_locked_by_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  tasks_summary:
-    failed: 0
-    todo: 3
-    running: 1
-    done: 1
-  runtime_constraints: {}
-  state: Cancelled
-  script_parameters_digest: 99914b932bd37a50b983c5e7c90ae93b
-
-job_in_subproject:
-  uuid: zzzzz-8i9sb-subprojectjob01
-  created_at: 2014-10-15 12:00:00
-  owner_uuid: zzzzz-j7d0g-axqo7eu9pwvna1x
-  log: ~
-  repository: active/foo
-  script: hash
-  script_version: 4fe459abe02d9b365932b8f5dc419439ab4e2577
-  state: Complete
-  script_parameters_digest: 99914b932bd37a50b983c5e7c90ae93b
-
-job_in_trashed_project:
-  uuid: zzzzz-8i9sb-subprojectjob02
-  created_at: 2014-10-15 12:00:00
-  owner_uuid: zzzzz-j7d0g-trashedproject2
-  log: ~
-  repository: active/foo
-  script: hash
-  script_version: 4fe459abe02d9b365932b8f5dc419439ab4e2577
-  state: Complete
-  script_parameters_digest: 99914b932bd37a50b983c5e7c90ae93b
-
-running_will_be_completed:
-  uuid: zzzzz-8i9sb-rshmckwoma9pjh8
-  owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
-  cancelled_at: ~
-  cancelled_by_user_uuid: ~
-  cancelled_by_client_uuid: ~
-  created_at: <%= 3.minute.ago.to_fs(:db) %>
-  started_at: <%= 3.minute.ago.to_fs(:db) %>
-  finished_at: ~
-  script_version: 1de84a854e2b440dc53bf42f8548afa4c17da332
-  running: true
-  success: ~
-  output: ~
-  priority: 0
-  log: ~
-  is_locked_by_uuid: zzzzz-tpzed-d9tiejq69daie8f
-  tasks_summary:
-    failed: 0
-    todo: 3
-    running: 1
-    done: 1
-  runtime_constraints: {}
-  state: Running
-  script_parameters_digest: 99914b932bd37a50b983c5e7c90ae93b
-
-graph_stage1:
-  uuid: zzzzz-8i9sb-graphstage10000
-  owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
-  repository: active/foo
-  script: hash
-  script_version: 4fe459abe02d9b365932b8f5dc419439ab4e2577
-  state: Complete
-  output: fa7aeb5140e2848d39b416daeef4ffc5+45
-  script_parameters_digest: 99914b932bd37a50b983c5e7c90ae93b
-
-graph_stage2:
-  uuid: zzzzz-8i9sb-graphstage20000
-  owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
-  repository: active/foo
-  script: hash2
-  script_version: 4fe459abe02d9b365932b8f5dc419439ab4e2577
-  state: Complete
-  script_parameters:
-    input: fa7aeb5140e2848d39b416daeef4ffc5+45
-    input2: "stuff"
-  output: 65b17c95fdbc9800fc48acda4e9dcd0b+93
-  script_parameters_digest: 4900033ec5cfaf8a63566f3664aeaa70
-
-graph_stage3:
-  uuid: zzzzz-8i9sb-graphstage30000
-  owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
-  repository: active/foo
-  script: hash2
-  script_version: 4fe459abe02d9b365932b8f5dc419439ab4e2577
-  state: Complete
-  script_parameters:
-    input: fa7aeb5140e2848d39b416daeef4ffc5+45
-    input2: "stuff2"
-  output: ea10d51bcf88862dbcc36eb292017dfd+45
-  script_parameters_digest: 02a085407e751d00b5dc88f1bd5e8247
-
-job_with_latest_version:
-  uuid: zzzzz-8i9sb-nj8ioxnrvjtyk2b
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  cancelled_at: ~
-  cancelled_by_user_uuid: ~
-  cancelled_by_client_uuid: ~
-  script: hash
-  repository: active/foo
-  script_version: 7def43a4d3f20789dda4700f703b5514cc3ed250
-  supplied_script_version: main
-  script_parameters:
-    input: 1f4b0bc7583c2a7f9102c395f4ffc5e3+45
-  created_at: <%= 3.minute.ago.to_fs(:db) %>
-  started_at: <%= 2.minute.ago.to_fs(:db) %>
-  finished_at: <%= 1.minute.ago.to_fs(:db) %>
-  running: false
-  success: true
-  output: fa7aeb5140e2848d39b416daeef4ffc5+45
-  priority: 0
-  log: ea10d51bcf88862dbcc36eb292017dfd+45
-  is_locked_by_uuid: ~
-  tasks_summary:
-    failed: 0
-    todo: 0
-    running: 0
-    done: 1
-  runtime_constraints: {}
-  state: Complete
-  script_parameters_digest: 03a43a7d84f7fb022467b876c2950acd
-
-running_job_in_publicly_accessible_project:
-  uuid: zzzzz-8i9sb-n7omg50bvt0m1nf
-  owner_uuid: zzzzz-j7d0g-zhxawtyetzwc5f0
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  repository: active/bar
-  script: running_job_script
-  script_version: 4fe459abe02d9b365932b8f5dc419439ab4e2577
-  state: Running
-  script_parameters:
-    input: fa7aeb5140e2848d39b416daeef4ffc5+45
-    input2: "stuff2"
-  script_parameters_digest: 02a085407e751d00b5dc88f1bd5e8247
-
-completed_job_in_publicly_accessible_project:
-  uuid: zzzzz-8i9sb-jyq01m7in1jlofj
-  owner_uuid: zzzzz-j7d0g-zhxawtyetzwc5f0
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  repository: active/foo
-  script: completed_job_script
-  script_version: 4fe459abe02d9b365932b8f5dc419439ab4e2577
-  state: Complete
-  script_parameters:
-    input: fa7aeb5140e2848d39b416daeef4ffc5+45
-    input2: "stuff2"
-  log: zzzzz-4zz18-4en62shvi99lxd4
-  output: b519d9cb706a29fc7ea24dbea2f05851+93
-  script_parameters_digest: 02a085407e751d00b5dc88f1bd5e8247
-  started_at: <%= 10.minute.ago.to_fs(:db) %>
-  finished_at: <%= 5.minute.ago.to_fs(:db) %>
-
-job_in_publicly_accessible_project_but_other_objects_elsewhere:
-  uuid: zzzzz-8i9sb-jyq01muyhgr4ofj
-  owner_uuid: zzzzz-j7d0g-zhxawtyetzwc5f0
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  repository: active/foo
-  script: completed_job_script
-  script_version: 4fe459abe02d9b365932b8f5dc419439ab4e2577
-  state: Complete
-  script_parameters:
-    input: fa7aeb5140e2848d39b416daeef4ffc5+45
-    input2: "stuff2"
-  log: zzzzz-4zz18-fy296fx3hot09f7
-  output: zzzzz-4zz18-bv31uwvy3neko21
-  script_parameters_digest: 02a085407e751d00b5dc88f1bd5e8247
-
-running_job_with_components:
-  uuid: zzzzz-8i9sb-with2components
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  cancelled_at: ~
-  cancelled_by_user_uuid: ~
-  cancelled_by_client_uuid: ~
-  created_at: <%= 3.minute.ago.to_fs(:db) %>
-  started_at: <%= 3.minute.ago.to_fs(:db) %>
-  finished_at: ~
-  script: hash
-  repository: active/foo
-  script_version: 1de84a854e2b440dc53bf42f8548afa4c17da332
-  running: true
-  success: ~
-  output: ~
-  priority: 0
-  log: ~
-  is_locked_by_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  tasks_summary:
-    failed: 0
-    todo: 3
-    running: 1
-    done: 1
-  runtime_constraints: {}
-  state: Running
-  components:
-    component1: zzzzz-8i9sb-jyq01m7in1jlofj
-    component2: zzzzz-d1hrv-partdonepipelin
-  script_parameters_digest: 99914b932bd37a50b983c5e7c90ae93b
-
-# This main level job is in running state with one job and one pipeline instance components
-running_job_with_components_at_level_1:
-  uuid: zzzzz-8i9sb-jobcomponentsl1
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  cancelled_at: ~
-  cancelled_by_user_uuid: ~
-  cancelled_by_client_uuid: ~
-  created_at: <%= 12.hour.ago.to_fs(:db) %>
-  started_at: <%= 12.hour.ago.to_fs(:db) %>
-  finished_at: ~
-  repository: active/foo
-  script: hash
-  script_version: 1de84a854e2b440dc53bf42f8548afa4c17da332
-  script_parameters_digest: 99914b932bd37a50b983c5e7c90ae93b
-  running: true
-  success: ~
-  output: ~
-  priority: 0
-  log: ~
-  is_locked_by_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  tasks_summary:
-    failed: 0
-    todo: 3
-    running: 1
-    done: 1
-  runtime_constraints: {}
-  state: Running
-  components:
-    component1: zzzzz-8i9sb-jobcomponentsl2
-    component2: zzzzz-d1hrv-picomponentsl02
-
-# This running job, a child of level_1, has one child component
-running_job_with_components_at_level_2:
-  uuid: zzzzz-8i9sb-jobcomponentsl2
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  cancelled_at: ~
-  cancelled_by_user_uuid: ~
-  cancelled_by_client_uuid: ~
-  created_at: <%= 12.hour.ago.to_fs(:db) %>
-  started_at: <%= 12.hour.ago.to_fs(:db) %>
-  finished_at: ~
-  repository: active/foo
-  script: hash
-  script_version: 1de84a854e2b440dc53bf42f8548afa4c17da332
-  script_parameters_digest: 99914b932bd37a50b983c5e7c90ae93b
-  running: true
-  success: ~
-  output: ~
-  priority: 0
-  log: ~
-  is_locked_by_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  tasks_summary:
-    failed: 0
-    todo: 3
-    running: 1
-    done: 1
-  runtime_constraints: {}
-  state: Running
-  components:
-    component1: zzzzz-8i9sb-job1atlevel3noc
-
-# The below two running jobs, children of level_2, have no child components
-running_job_1_with_components_at_level_3:
-  uuid: zzzzz-8i9sb-job1atlevel3noc
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  cancelled_at: ~
-  cancelled_by_user_uuid: ~
-  cancelled_by_client_uuid: ~
-  created_at: <%= 12.hour.ago.to_fs(:db) %>
-  started_at: <%= 12.hour.ago.to_fs(:db) %>
-  finished_at: ~
-  repository: active/foo
-  script: hash
-  script_version: 1de84a854e2b440dc53bf42f8548afa4c17da332
-  script_parameters_digest: 99914b932bd37a50b983c5e7c90ae93b
-  running: true
-  success: ~
-  output: ~
-  priority: 0
-  log: ~
-  is_locked_by_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  tasks_summary:
-    failed: 0
-    todo: 3
-    running: 1
-    done: 1
-  runtime_constraints: {}
-  state: Running
-
-running_job_2_with_components_at_level_3:
-  uuid: zzzzz-8i9sb-job2atlevel3noc
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  cancelled_at: ~
-  cancelled_by_user_uuid: ~
-  cancelled_by_client_uuid: ~
-  created_at: <%= 12.hour.ago.to_fs(:db) %>
-  started_at: <%= 12.hour.ago.to_fs(:db) %>
-  finished_at: ~
-  repository: active/foo
-  script: hash
-  script_version: 1de84a854e2b440dc53bf42f8548afa4c17da332
-  script_parameters_digest: 99914b932bd37a50b983c5e7c90ae93b
-  running: true
-  success: ~
-  output: ~
-  priority: 0
-  log: ~
-  is_locked_by_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  tasks_summary:
-    failed: 0
-    todo: 3
-    running: 1
-    done: 1
-  runtime_constraints: {}
-  state: Running
-
-# The two jobs below are so confused, they have circular relationship
-running_job_1_with_circular_component_relationship:
-  uuid: zzzzz-8i9sb-job1withcirculr
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  cancelled_at: ~
-  cancelled_by_user_uuid: ~
-  cancelled_by_client_uuid: ~
-  created_at: <%= 12.hour.ago.to_fs(:db) %>
-  started_at: <%= 12.hour.ago.to_fs(:db) %>
-  finished_at: ~
-  repository: active/foo
-  script: hash
-  script_version: 1de84a854e2b440dc53bf42f8548afa4c17da332
-  script_parameters_digest: 99914b932bd37a50b983c5e7c90ae93b
-  running: true
-  success: ~
-  output: ~
-  priority: 0
-  log: ~
-  is_locked_by_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  tasks_summary:
-    failed: 0
-    todo: 3
-    running: 1
-    done: 1
-  runtime_constraints: {}
-  state: Running
-  components:
-    component1: zzzzz-8i9sb-job2withcirculr
-
-running_job_2_with_circular_component_relationship:
-  uuid: zzzzz-8i9sb-job2withcirculr
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  cancelled_at: ~
-  cancelled_by_user_uuid: ~
-  cancelled_by_client_uuid: ~
-  created_at: <%= 12.hour.ago.to_fs(:db) %>
-  started_at: <%= 12.hour.ago.to_fs(:db) %>
-  finished_at: ~
-  repository: active/foo
-  script: hash
-  script_version: 1de84a854e2b440dc53bf42f8548afa4c17da332
-  script_parameters_digest: 99914b932bd37a50b983c5e7c90ae93b
-  running: true
-  success: ~
-  output: ~
-  priority: 0
-  log: ~
-  is_locked_by_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  tasks_summary:
-    failed: 0
-    todo: 3
-    running: 1
-    done: 1
-  runtime_constraints: {}
-  state: Running
-  components:
-    component1: zzzzz-8i9sb-job1withcirculr
diff --git a/services/api/test/fixtures/keep_disks.yml b/services/api/test/fixtures/keep_disks.yml
deleted file mode 100644
index 5cccf498af..0000000000
--- a/services/api/test/fixtures/keep_disks.yml
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-nonfull:
-  uuid: zzzzz-penuu-5w2o2t1q5wy7fhn
-  owner_uuid: zzzzz-tpzed-d9tiejq69daie8f
-  node_uuid: zzzzz-7ekkf-53y36l1lu5ijveb
-  keep_service_uuid: zzzzz-bi6l4-6zhilxar6r8ey90
-  last_read_at: <%= 1.minute.ago.to_fs(:db) %>
-  last_write_at: <%= 2.minute.ago.to_fs(:db) %>
-  last_ping_at: <%= 3.minute.ago.to_fs(:db) %>
-  ping_secret: z9xz2tc69dho51g1dmkdy5fnupdhsprahcwxdbjs0zms4eo6i
-
-full:
-  uuid: zzzzz-penuu-4kmq58ui07xuftx
-  owner_uuid: zzzzz-tpzed-d9tiejq69daie8f
-  node_uuid: zzzzz-7ekkf-53y36l1lu5ijveb
-  keep_service_uuid: zzzzz-bi6l4-6zhilxar6r8ey90
-  last_read_at: <%= 1.minute.ago.to_fs(:db) %>
-  last_write_at: <%= 2.day.ago.to_fs(:db) %>
-  last_ping_at: <%= 3.minute.ago.to_fs(:db) %>
-  ping_secret: xx3ieejcufbjy4lli6yt5ig4e8w5l2hhgmbyzpzuq38gri6lj
-
-nonfull2:
-  uuid: zzzzz-penuu-1ydrih9k2er5j11
-  owner_uuid: zzzzz-tpzed-d9tiejq69daie8f
-  node_uuid: zzzzz-7ekkf-2z3mc76g2q73aio
-  keep_service_uuid: zzzzz-bi6l4-rsnj3c76ndxb7o0
-  last_read_at: <%= 1.minute.ago.to_fs(:db) %>
-  last_write_at: <%= 2.minute.ago.to_fs(:db) %>
-  last_ping_at: <%= 3.minute.ago.to_fs(:db) %>
-  ping_secret: 4rs260ibhdum1d242xy23qv320rlerc0j7qg9vyqnchbgmjeek
diff --git a/services/api/test/fixtures/links.yml b/services/api/test/fixtures/links.yml
index 00d5971534..61ad60451d 100644
--- a/services/api/test/fixtures/links.yml
+++ b/services/api/test/fixtures/links.yml
@@ -254,104 +254,6 @@ baz_file_publicly_readable:
   head_uuid: zzzzz-4zz18-y9vne9npefyxh8g
   properties: {}
 
-barbaz_job_readable_by_spectator:
-  uuid: zzzzz-o0j2j-cpy7p41hpk531e1
-  owner_uuid: zzzzz-tpzed-000000000000000
-  created_at: 2014-01-24 20:42:26 -0800
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-000000000000000
-  modified_at: 2014-01-24 20:42:26 -0800
-  updated_at: 2014-01-24 20:42:26 -0800
-  tail_uuid: zzzzz-tpzed-l1s2piq4t4mps8r
-  link_class: permission
-  name: can_read
-  head_uuid: zzzzz-8i9sb-cjs4pklxxjykyuq
-  properties: {}
-
-runningbarbaz_job_readable_by_spectator:
-  uuid: zzzzz-o0j2j-cpy7p41hpk531e2
-  owner_uuid: zzzzz-tpzed-000000000000000
-  created_at: 2014-01-24 20:42:26 -0800
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-000000000000000
-  modified_at: 2014-01-24 20:42:26 -0800
-  updated_at: 2014-01-24 20:42:26 -0800
-  tail_uuid: zzzzz-tpzed-l1s2piq4t4mps8r
-  link_class: permission
-  name: can_read
-  head_uuid: zzzzz-8i9sb-cjs4pklxxjykyuj
-  properties: {}
-
-arvados_repository_readable_by_all_users:
-  uuid: zzzzz-o0j2j-allcanreadarvrp
-  owner_uuid: zzzzz-tpzed-000000000000000
-  created_at: 2014-01-24 20:42:26 -0800
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-000000000000000
-  modified_at: 2014-01-24 20:42:26 -0800
-  updated_at: 2014-01-24 20:42:26 -0800
-  tail_uuid: zzzzz-j7d0g-fffffffffffffff
-  link_class: permission
-  name: can_read
-  head_uuid: zzzzz-s0uqq-arvadosrepo0123
-  properties: {}
-
-foo_repository_readable_by_spectator:
-  uuid: zzzzz-o0j2j-cpy7p41hpk5xxx
-  owner_uuid: zzzzz-tpzed-000000000000000
-  created_at: 2014-01-24 20:42:26 -0800
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-000000000000000
-  modified_at: 2014-01-24 20:42:26 -0800
-  updated_at: 2014-01-24 20:42:26 -0800
-  tail_uuid: zzzzz-tpzed-l1s2piq4t4mps8r
-  link_class: permission
-  name: can_read
-  head_uuid: zzzzz-s0uqq-382brsig8rp3666
-  properties: {}
-
-foo_repository_manageable_by_active:
-  uuid: zzzzz-o0j2j-8tdfjd8g0s4rn1k
-  owner_uuid: zzzzz-tpzed-000000000000000
-  created_at: 2014-01-24 20:42:26 -0800
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-000000000000000
-  modified_at: 2014-01-24 20:42:26 -0800
-  updated_at: 2014-01-24 20:42:26 -0800
-  tail_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  link_class: permission
-  name: can_manage
-  head_uuid: zzzzz-s0uqq-382brsig8rp3666
-  properties: {}
-
-repository3_readable_by_active:
-  uuid: zzzzz-o0j2j-43iem9bdtefa76g
-  owner_uuid: zzzzz-tpzed-000000000000000
-  created_at: 2014-09-23 13:52:46 -0400
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-000000000000000
-  modified_at: 2014-09-23 13:52:46 -0400
-  updated_at: 2014-09-23 13:52:46 -0400
-  tail_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  link_class: permission
-  name: can_read
-  head_uuid: zzzzz-s0uqq-38orljkqpyo1j61
-  properties: {}
-
-repository4_writable_by_active:
-  uuid: zzzzz-o0j2j-lio9debdt6yhkil
-  owner_uuid: zzzzz-tpzed-000000000000000
-  created_at: 2014-09-23 13:52:46 -0400
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-000000000000000
-  modified_at: 2014-09-23 13:52:46 -0400
-  updated_at: 2014-09-23 13:52:46 -0400
-  tail_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  link_class: permission
-  name: can_write
-  head_uuid: zzzzz-s0uqq-38oru8hnk57ht34
-  properties: {}
-
 miniadmin_user_is_a_testusergroup_admin:
   uuid: zzzzz-o0j2j-38vvkciz7qc12j9
   owner_uuid: zzzzz-tpzed-000000000000000
@@ -784,81 +686,6 @@ docker_image_tag_like_hash:
   properties:
     image_timestamp: "2014-06-10T14:30:00.184019565Z"
 
-job_reader_can_read_previous_job_run:
-  # Permission link giving job_reader permission
-  # to read previous_job_run
-  uuid: zzzzz-o0j2j-8bbd851795ebafd
-  owner_uuid: zzzzz-tpzed-000000000000000
-  created_at: 2014-06-13 20:42:26 -0800
-  modified_by_client_uuid: zzzzz-tpzed-000000000000000
-  modified_by_user_uuid: zzzzz-tpzed-000000000000000
-  modified_at: 2014-06-13 20:42:26 -0800
-  updated_at: 2014-06-13 20:42:26 -0800
-  link_class: permission
-  name: can_read
-  tail_uuid: zzzzz-tpzed-905b42d1dd4a354
-  head_uuid: zzzzz-8i9sb-cjs4pklxxjykqqq
-
-job_reader_can_read_foo_repo:
-  # Permission link giving job_reader permission
-  # to read foo_repo
-  uuid: zzzzz-o0j2j-072ec05dc9487f8
-  owner_uuid: zzzzz-tpzed-000000000000000
-  created_at: 2014-06-13 20:42:26 -0800
-  modified_by_client_uuid: zzzzz-tpzed-000000000000000
-  modified_by_user_uuid: zzzzz-tpzed-000000000000000
-  modified_at: 2014-06-13 20:42:26 -0800
-  updated_at: 2014-06-13 20:42:26 -0800
-  link_class: permission
-  name: can_read
-  tail_uuid: zzzzz-tpzed-905b42d1dd4a354
-  head_uuid: zzzzz-s0uqq-382brsig8rp3666
-
-job_reader2_can_read_job_with_components:
-  # Permission link giving job_reader2 permission
-  # to read running_job_with_components
-  uuid: zzzzz-o0j2j-jobcomps4jobrdr
-  owner_uuid: zzzzz-tpzed-000000000000000
-  created_at: 2014-06-13 20:42:26 -0800
-  modified_by_client_uuid: zzzzz-tpzed-000000000000000
-  modified_by_user_uuid: zzzzz-tpzed-000000000000000
-  modified_at: 2014-06-13 20:42:26 -0800
-  updated_at: 2014-06-13 20:42:26 -0800
-  link_class: permission
-  name: can_read
-  tail_uuid: zzzzz-tpzed-readjobwithcomp
-  head_uuid: zzzzz-8i9sb-with2components
-
-job_reader2_can_read_pipeline_from_job_with_components:
-  # Permission link giving job_reader2 permission
-  # to read running_job_with_components
-  uuid: zzzzz-o0j2j-pi4comps4jobrdr
-  owner_uuid: zzzzz-tpzed-000000000000000
-  created_at: 2014-06-13 20:42:26 -0800
-  modified_by_client_uuid: zzzzz-tpzed-000000000000000
-  modified_by_user_uuid: zzzzz-tpzed-000000000000000
-  modified_at: 2014-06-13 20:42:26 -0800
-  updated_at: 2014-06-13 20:42:26 -0800
-  link_class: permission
-  name: can_read
-  tail_uuid: zzzzz-tpzed-readjobwithcomp
-  head_uuid: zzzzz-d1hrv-partdonepipelin
-
-job_reader2_can_read_first_job_from_pipeline_from_job_with_components:
-  # Permission link giving job_reader2 permission
-  # to read running_job_with_components
-  uuid: zzzzz-o0j2j-job4pi4j4jobrdr
-  owner_uuid: zzzzz-tpzed-000000000000000
-  created_at: 2014-06-13 20:42:26 -0800
-  modified_by_client_uuid: zzzzz-tpzed-000000000000000
-  modified_by_user_uuid: zzzzz-tpzed-000000000000000
-  modified_at: 2014-06-13 20:42:26 -0800
-  updated_at: 2014-06-13 20:42:26 -0800
-  link_class: permission
-  name: can_read
-  tail_uuid: zzzzz-tpzed-readjobwithcomp
-  head_uuid: zzzzz-8i9sb-cjs4pklxxjykqqq
-
 baz_collection_name_in_asubproject:
   uuid: zzzzz-o0j2j-bazprojectname2
   owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
diff --git a/services/api/test/fixtures/logs.yml b/services/api/test/fixtures/logs.yml
index 3b41550ae7..ee24d2ac64 100644
--- a/services/api/test/fixtures/logs.yml
+++ b/services/api/test/fixtures/logs.yml
@@ -11,22 +11,22 @@ noop: # nothing happened ...to the 'spectator' user
   event_at: <%= 1.minute.ago.to_fs(:db) %>
   created_at: <%= 1.minute.ago.to_fs(:db) %>
 
-admin_changes_repository2: # admin changes repository2, which is owned by active user
+admin_changes_collection_owned_by_active:
   id: 2
   uuid: zzzzz-57u5n-pshmckwoma00002
   owner_uuid: zzzzz-tpzed-d9tiejq69daie8f # admin user
-  object_uuid: zzzzz-2x53u-382brsig8rp3667 # repository foo
+  object_uuid: zzzzz-4zz18-bv31uwvy3neko21 # collection_owned_by_active
   object_owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz # active user
   created_at: <%= 2.minute.ago.to_fs(:db) %>
   event_at: <%= 2.minute.ago.to_fs(:db) %>
   event_type: update
 
-admin_changes_specimen: # admin changes specimen owned_by_spectator
+admin_changes_collection_owned_by_foo:
   id: 3
   uuid: zzzzz-57u5n-pshmckwoma00003
   owner_uuid: zzzzz-tpzed-d9tiejq69daie8f # admin user
-  object_uuid: zzzzz-2x53u-3b0xxwzlbzxq5yr # specimen owned_by_spectator
-  object_owner_uuid: zzzzz-tpzed-l1s2piq4t4mps8r # spectator user
+  object_uuid: zzzzz-4zz18-50surkhkbhsp31b # collection_owned_by_foo
+  object_owner_uuid: zzzzz-tpzed-81hsbo6mk8nl05c # foo user
   created_at: <%= 3.minute.ago.to_fs(:db) %>
   event_at: <%= 3.minute.ago.to_fs(:db) %>
   event_type: update
@@ -60,101 +60,6 @@ log_owned_by_active:
   event_at: <%= 2.minute.ago.to_fs(:db) %>
   summary: non-admin use can read own logs
 
-crunchstat_for_running_job:
-  id: 7
-  uuid: zzzzz-57u5n-tmymyrojrbtnxh1
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  modified_by_client_uuid: zzzzz-ozdt8-obw7foaks3qjyej
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  object_uuid: zzzzz-8i9sb-pshmckwoma9plh7
-  event_at: 2014-11-07 23:33:42.347455000 Z
-  event_type: stderr
-  summary: ~
-  properties:
-    text: '2014-11-07_23:33:41 zzzzz-8i9sb-pshmckwoma9plh7 31708 1 stderr crunchstat:
-      cpu 1935.4300 user 59.4100 sys 8 cpus -- interval 10.0002 seconds 12.9900 user
-      0.9900 sys'
-  created_at: 2014-11-07 23:33:42.351913000 Z
-  updated_at: 2014-11-07 23:33:42.347455000 Z
-  modified_at: 2014-11-07 23:33:42.347455000 Z
-  object_owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
-
-log_line_for_pipeline_in_publicly_accessible_project:
-  id: 8
-  uuid: zzzzz-57u5n-tmymyrojrjyhb45
-  owner_uuid: zzzzz-j7d0g-zhxawtyetzwc5f0
-  modified_by_client_uuid: zzzzz-ozdt8-obw7foaks3qjyej
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  object_uuid: zzzzz-d1hrv-n68vc490mloy4fi
-  event_at: 2014-11-07 23:33:42.347455000 Z
-  event_type: stderr
-  summary: ~
-  properties:
-    text: '2014-11-07_23:33:41 zzzzz-d1hrv-n68vc490mloy4fi 31708 1 stderr crunchstat:
-      cpu 1935.4300 user 59.4100 sys 8 cpus -- interval 10.0002 seconds 12.9900 user
-      0.9900 sys'
-  created_at: 2014-11-07 23:33:42.351913000 Z
-  updated_at: 2014-11-07 23:33:42.347455000 Z
-  modified_at: 2014-11-07 23:33:42.347455000 Z
-  object_owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
-
-log_line_for_pipeline_in_publicly_accessible_project_but_other_objects_elsewhere:
-  id: 9
-  uuid: zzzzz-57u5n-tmyhy56k9lnhb45
-  owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
-  modified_by_client_uuid: zzzzz-ozdt8-obw7foaks3qjyej
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  object_uuid: zzzzz-d1hrv-pisharednotobjs
-  event_at: 2014-11-07 23:33:42.347455000 Z
-  event_type: stderr
-  summary: ~
-  properties:
-    text: '2014-11-07_23:33:41 zzzzz-d1hrv-pisharednotobjs 31708 1 stderr crunchstat:
-      cpu 1935.4300 user 59.4100 sys 8 cpus -- interval 10.0002 seconds 12.9900 user
-      0.9900 sys'
-  created_at: 2014-11-07 23:33:42.351913000 Z
-  updated_at: 2014-11-07 23:33:42.347455000 Z
-  modified_at: 2014-11-07 23:33:42.347455000 Z
-  object_owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
-
-crunchstat_for_previous_job:
-  id: 10
-  uuid: zzzzz-57u5n-eir3aesha3kaene
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  modified_by_client_uuid: zzzzz-ozdt8-obw7foaks3qjyej
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  object_uuid: zzzzz-8i9sb-cjs4pklxxjykqqq
-  event_at: 2014-11-07 23:33:42.347455000 Z
-  event_type: stderr
-  summary: ~
-  properties:
-    text: '2014-11-07_23:33:41 zzzzz-8i9sb-cjs4pklxxjykqqq 11592 1 stderr crunchstat:
-      cpu 1935.4300 user 59.4100 sys 8 cpus -- interval 10.0002 seconds 12.9900 user
-      0.9900 sys'
-  created_at: 2014-11-07 23:33:42.351913000 Z
-  updated_at: 2014-11-07 23:33:42.347455000 Z
-  modified_at: 2014-11-07 23:33:42.347455000 Z
-  object_owner_uuid: zzzzz-j7d0g-xurymjxw79nv3jz
-
-crunchstat_for_ancient_job:
-  id: 11
-  uuid: zzzzz-57u5n-ixioph7ieb5ung8
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  modified_by_client_uuid: zzzzz-ozdt8-obw7foaks3qjyej
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  object_uuid: zzzzz-8i9sb-ahd7cie8jah9qui
-  event_at: 2013-11-07 23:33:42.347455000 Z
-  event_type: stderr
-  summary: ~
-  properties:
-    text: '2013-11-07_23:33:41 zzzzz-8i9sb-ahd7cie8jah9qui 29610 1 stderr crunchstat:
-      cpu 1935.4300 user 59.4100 sys 8 cpus -- interval 10.0002 seconds 12.9900 user
-      0.9900 sys'
-  created_at: 2013-11-07 23:33:42.351913000 Z
-  updated_at: 2013-11-07 23:33:42.347455000 Z
-  modified_at: 2013-11-07 23:33:42.347455000 Z
-  object_owner_uuid: zzzzz-j7d0g-xurymjxw79nv3jz
-
 stderr_for_ancient_container:
   id: 12
   uuid: zzzzz-57u5n-containerlog001
@@ -166,7 +71,7 @@ stderr_for_ancient_container:
   event_type: stderr
   summary: ~
   properties:
-    text: '2013-11-07_23:33:41 zzzzz-8i9sb-ahd7cie8jah9qui 29610 1 stderr crunchstat:
+    text: '2013-11-07_23:33:41 zzzzz-dz642-logscontainer01 29610 1 stderr crunchstat:
       cpu 1935.4300 user 59.4100 sys 8 cpus -- interval 10.0002 seconds 12.9900 user
       0.9900 sys'
   created_at: <%= 2.year.ago.to_fs(:db) %>
@@ -185,7 +90,7 @@ crunchstat_for_ancient_container:
   event_type: crunchstat
   summary: ~
   properties:
-    text: '2013-11-07_23:33:41 zzzzz-8i9sb-ahd7cie8jah9qui 29610 1 stderr crunchstat:
+    text: '2013-11-07_23:33:41 zzzzz-dz642-logscontainer01 29610 1 stderr crunchstat:
       cpu 1935.4300 user 59.4100 sys 8 cpus -- interval 10.0002 seconds 12.9900 user
       0.9900 sys'
   created_at: <%= 2.year.ago.to_fs(:db) %>
@@ -204,7 +109,7 @@ stderr_for_previous_container:
   event_type: stderr
   summary: ~
   properties:
-    text: '2013-11-07_23:33:41 zzzzz-8i9sb-ahd7cie8jah9qui 29610 1 stderr crunchstat:
+    text: '2013-11-07_23:33:41 zzzzz-dz642-logscontainer02 29610 1 stderr crunchstat:
       cpu 1935.4300 user 59.4100 sys 8 cpus -- interval 10.0002 seconds 12.9900 user
       0.9900 sys'
   created_at: <%= 1.month.ago.to_fs(:db) %>
@@ -223,7 +128,7 @@ crunchstat_for_previous_container:
   event_type: crunchstat
   summary: ~
   properties:
-    text: '2013-11-07_23:33:41 zzzzz-8i9sb-ahd7cie8jah9qui 29610 1 stderr crunchstat:
+    text: '2013-11-07_23:33:41 zzzzz-dz642-logscontainer02 29610 1 stderr crunchstat:
       cpu 1935.4300 user 59.4100 sys 8 cpus -- interval 10.0002 seconds 12.9900 user
       0.9900 sys'
   created_at: <%= 1.month.ago.to_fs(:db) %>
@@ -242,7 +147,7 @@ stderr_for_running_container:
   event_type: crunchstat
   summary: ~
   properties:
-    text: '2013-11-07_23:33:41 zzzzz-8i9sb-ahd7cie8jah9qui 29610 1 stderr crunchstat:
+    text: '2013-11-07_23:33:41 zzzzz-dz642-logscontainer03 29610 1 stderr crunchstat:
       cpu 1935.4300 user 59.4100 sys 8 cpus -- interval 10.0002 seconds 12.9900 user
       0.9900 sys'
   created_at: <%= 1.hour.ago.to_fs(:db) %>
@@ -261,7 +166,7 @@ crunchstat_for_running_container:
   event_type: crunchstat
   summary: ~
   properties:
-    text: '2013-11-07_23:33:41 zzzzz-8i9sb-ahd7cie8jah9qui 29610 1 stderr crunchstat:
+    text: '2013-11-07_23:33:41 zzzzz-dz642-logscontainer03 29610 1 stderr crunchstat:
       cpu 1935.4300 user 59.4100 sys 8 cpus -- interval 10.0002 seconds 12.9900 user
       0.9900 sys'
   created_at: <%= 1.hour.ago.to_fs(:db) %>
diff --git a/services/api/test/fixtures/nodes.yml b/services/api/test/fixtures/nodes.yml
deleted file mode 100644
index d4589ed705..0000000000
--- a/services/api/test/fixtures/nodes.yml
+++ /dev/null
@@ -1,102 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-busy:
-  uuid: zzzzz-7ekkf-53y36l1lu5ijveb
-  owner_uuid: zzzzz-tpzed-000000000000000
-  hostname: compute0
-  slot_number: 0
-  domain: ""
-  ip_address: 172.17.2.172
-  last_ping_at: <%= 1.minute.ago.to_fs(:db) %>
-  first_ping_at: <%= 23.hour.ago.to_fs(:db) %>
-  job_uuid: zzzzz-8i9sb-2gx6rz0pjl033w3  # nearly_finished_job
-  properties: {}
-  info:
-    ping_secret: "48dpm3b8ijyj3jkr2yczxw0844dqd2752bhll7klodvgz9bg80"
-    slurm_state: "alloc"
-
-down:
-  uuid: zzzzz-7ekkf-2vbompg3ecc6e2s
-  owner_uuid: zzzzz-tpzed-000000000000000
-  hostname: compute1
-  slot_number: 1
-  domain: ""
-  ip_address: 172.17.2.173
-  last_ping_at: <%= 1.hour.ago.to_fs(:db) %>
-  first_ping_at: <%= 23.hour.ago.to_fs(:db) %>
-  job_uuid: ~
-  properties: {}
-  info:
-    ping_secret: "2k3i71depad36ugwmlgzilbi4e8n0illb2r8l4efg9mzkb3a1k"
-
-idle:
-  uuid: zzzzz-7ekkf-2z3mc76g2q73aio
-  owner_uuid: zzzzz-tpzed-000000000000000
-  hostname: compute2
-  slot_number: 2
-  domain: ""
-  ip_address: 172.17.2.174
-  last_ping_at: <%= 2.minute.ago.to_fs(:db) %>
-  first_ping_at: <%= 23.hour.ago.to_fs(:db) %>
-  job_uuid: ~
-  info:
-    ping_secret: "69udawxvn3zzj45hs8bumvndricrha4lcpi23pd69e44soanc0"
-    slurm_state: "idle"
-  properties:
-    total_cpu_cores: 16
-
-was_idle_now_down:
-  uuid: zzzzz-7ekkf-xuzpkdasl0uzwyz
-  owner_uuid: zzzzz-tpzed-000000000000000
-  hostname: compute3
-  slot_number: ~
-  domain: ""
-  ip_address: 172.17.2.174
-  last_ping_at: <%= 1.hour.ago.to_fs(:db) %>
-  first_ping_at: <%= 23.hour.ago.to_fs(:db) %>
-  job_uuid: ~
-  info:
-    ping_secret: "1bd1yi0x4lb5q4gzqqtrnq30oyj08r8dtdimmanbqw49z1anz2"
-    slurm_state: "idle"
-  properties:
-    total_cpu_cores: 16
-
-new_with_no_hostname:
-  uuid: zzzzz-7ekkf-newnohostname00
-  owner_uuid: zzzzz-tpzed-000000000000000
-  hostname: ~
-  slot_number: ~
-  ip_address: 172.17.2.175
-  last_ping_at: ~
-  first_ping_at: ~
-  job_uuid: ~
-  properties: {}
-  info:
-    ping_secret: "abcdyi0x4lb5q4gzqqtrnq30oyj08r8dtdimmanbqw49z1anz2"
-
-new_with_custom_hostname:
-  uuid: zzzzz-7ekkf-newwithhostname
-  owner_uuid: zzzzz-tpzed-000000000000000
-  hostname: custom1
-  slot_number: 23
-  ip_address: 172.17.2.176
-  last_ping_at: ~
-  first_ping_at: ~
-  job_uuid: ~
-  properties: {}
-  info:
-    ping_secret: "abcdyi0x4lb5q4gzqqtrnq30oyj08r8dtdimmanbqw49z1anz2"
-
-node_with_no_ip_address_yet:
-  uuid: zzzzz-7ekkf-nodenoipaddryet
-  owner_uuid: zzzzz-tpzed-000000000000000
-  hostname: noipaddr
-  slot_number: ~
-  last_ping_at: ~
-  first_ping_at: ~
-  job_uuid: ~
-  properties: {}
-  info:
-    ping_secret: "abcdyefg4lb5q4gzqqtrnq30oyj08r8dtdimmanbqw49z1anz2"
diff --git a/services/api/test/fixtures/pipeline_instances.yml b/services/api/test/fixtures/pipeline_instances.yml
deleted file mode 100644
index 714fc60771..0000000000
--- a/services/api/test/fixtures/pipeline_instances.yml
+++ /dev/null
@@ -1,530 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-new_pipeline:
-  state: New
-  uuid: zzzzz-d1hrv-f4gneyn6br1xize
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: <%= 1.minute.ago.to_fs(:db) %>
-
-new_pipeline_in_subproject:
-  state: New
-  uuid: zzzzz-d1hrv-subprojpipeline
-  owner_uuid: zzzzz-j7d0g-axqo7eu9pwvna1x
-  created_at: <%= 1.minute.ago.to_fs(:db) %>
-
-has_component_with_no_script_parameters:
-  state: Ready
-  uuid: zzzzz-d1hrv-1xfj6xkicf2muk2
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: <%= 10.minute.ago.to_fs(:db) %>
-  components:
-   foo:
-    script: foo
-    script_version: main
-    script_parameters: {}
-
-has_component_with_empty_script_parameters:
-  state: Ready
-  uuid: zzzzz-d1hrv-jq16l10gcsnyumo
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: <%= 3.minute.ago.to_fs(:db) %>
-  components:
-   foo:
-    script: foo
-    script_version: main
-
-has_component_with_completed_jobs:
-  # Test that the job "started_at" and "finished_at" fields are parsed
-  # into Time fields when rendering. These jobs must *not* have their
-  # own fixtures; the point is to force the
-  # pipeline_instances_controller_test in Workbench to parse the
-  # "components" field. (The relevant code paths are also used when a
-  # user has permission to read the pipeline instance itself, but not
-  # the jobs referenced by its components hash.)
-  state: Complete
-  uuid: zzzzz-d1hrv-i3e77t9z5y8j9cc
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: <%= 11.minute.ago.to_fs(:db) %>
-  started_at: <%= 10.minute.ago.to_fs(:db) %>
-  finished_at: <%= 9.minute.ago.to_fs(:db) %>
-  components:
-   foo:
-    script: foo
-    script_version: main
-    script_parameters: {}
-    job:
-      uuid: zzzzz-8i9sb-rft1xdewxkwgxnz
-      script_version: main
-      created_at: <%= 10.minute.ago.to_fs(:db) %>
-      started_at: <%= 10.minute.ago.to_fs(:db) %>
-      finished_at: <%= 9.minute.ago.to_fs(:db) %>
-      state: Complete
-      tasks_summary:
-        failed: 0
-        todo: 0
-        running: 0
-        done: 1
-   bar:
-    script: bar
-    script_version: main
-    script_parameters: {}
-    job:
-      uuid: zzzzz-8i9sb-r2dtbzr6bfread7
-      script_version: main
-      created_at: <%= 9.minute.ago.to_fs(:db) %>
-      started_at: <%= 9.minute.ago.to_fs(:db) %>
-      state: Running
-      tasks_summary:
-        failed: 0
-        todo: 1
-        running: 2
-        done: 3
-   baz:
-    script: baz
-    script_version: main
-    script_parameters: {}
-    job:
-      uuid: zzzzz-8i9sb-c7408rni11o7r6s
-      script_version: main
-      created_at: <%= 9.minute.ago.to_fs(:db) %>
-      state: Queued
-      tasks_summary: {}
-
-has_job:
-  name: pipeline_with_job
-  state: Ready
-  uuid: zzzzz-d1hrv-1yfj6xkidf2muk3
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: <%= 2.9.minute.ago.to_fs(:db) %>
-  components:
-   foo:
-    script: foo
-    script_version: main
-    script_parameters: {}
-    job: {
-            uuid: zzzzz-8i9sb-pshmckwoma9plh7,
-            script_version: main
-         }
-
-components_is_jobspec:
-  # Helps test that clients cope with funny-shaped components.
-  # For an example, see #3321.
-  uuid: zzzzz-d1hrv-1yfj61234abcdk4
-  created_at: <%= 4.minute.ago.to_fs(:db) %>
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  state: RunningOnServer
-  components:
-    script: foo
-    script_version: main
-    script_parameters:
-      input:
-        required: true
-        dataclass: Collection
-        title: "Foo/bar pair"
-        description: "Provide a collection containing at least two files."
-
-pipeline_with_tagged_collection_input:
-  name: pipeline_with_tagged_collection_input
-  state: Ready
-  uuid: zzzzz-d1hrv-1yfj61234abcdk3
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: <%= 3.2.minute.ago.to_fs(:db) %>
-  components:
-    part-one:
-      script_parameters:
-        input:
-          value: zzzzz-4zz18-znfnqtbbv4spc3w
-
-pipeline_to_merge_params:
-  name: pipeline_to_merge_params
-  state: Ready
-  uuid: zzzzz-d1hrv-1yfj6dcba4321k3
-  pipeline_template_uuid: zzzzz-p5p6p-aox0k0ofxrystgw
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: <%= 3.3.minute.ago.to_fs(:db) %>
-  components:
-    part-one:
-      script_parameters:
-        input:
-          required: true
-          dataclass: Collection
-          title: "Foo/bar pair"
-          description: "Provide a collection containing at least two files."
-    part-two:
-      script_parameters:
-        input:
-          output_of: part-one
-        integer_with_default:
-          default: 123
-        integer_with_value:
-          value: 123
-        string_with_default:
-          default: baz
-        string_with_value:
-          value: baz
-        plain_string: qux
-        array_with_default:
-          default: [1,1,2,3,5]
-        array_with_value:
-          value: [1,1,2,3,5]
-
-pipeline_with_newer_template:
-  state: Complete
-  uuid: zzzzz-d1hrv-9fm8l10i9z2kqc6
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  pipeline_template_uuid: zzzzz-p5p6p-vq4wuvy84xvaq2r
-  created_at: 2014-09-15 12:00:00
-  components:
-    foo:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: Collection
-          title: foo instance input
-
-pipeline_instance_owned_by_fuse:
-  state: Complete
-  uuid: zzzzz-d1hrv-ri9dvgkgqs9y09j
-  owner_uuid: zzzzz-tpzed-0fusedrivertest
-  pipeline_template_uuid: zzzzz-p5p6p-vq4wuvy84xvaq2r
-  created_at: 2014-09-16 12:00:00
-  name: "pipeline instance owned by FUSE"
-  components:
-    foo:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: Collection
-          title: foo instance input
-
-pipeline_instance_in_fuse_project:
-  state: Complete
-  uuid: zzzzz-d1hrv-scarxiyajtshq3l
-  owner_uuid: zzzzz-j7d0g-0000ownedbyfuse
-  pipeline_template_uuid: zzzzz-p5p6p-vq4wuvy84xvaq2r
-  created_at: 2014-09-17 12:00:00
-  name: "pipeline instance in FUSE project"
-  components:
-    foo:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: Collection
-          title: foo instance input
-
-pipeline_owned_by_active_in_aproject:
-  name: Completed pipeline in A Project
-  state: Complete
-  uuid: zzzzz-d1hrv-ju5ghi0i9z2kqc6
-  owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
-  created_at: 2014-09-18 12:00:00
-  components:
-    foo:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: Collection
-          title: foo instance input
-
-pipeline_owned_by_active_in_home:
-  name: Completed pipeline in active user home
-  state: Complete
-  uuid: zzzzz-d1hrv-lihrbd0i9z2kqc6
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: 2014-09-19 12:00:00
-  components:
-    foo:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: Collection
-          title: foo instance input
-
-pipeline_in_publicly_accessible_project:
-  uuid: zzzzz-d1hrv-n68vc490mloy4fi
-  owner_uuid: zzzzz-j7d0g-zhxawtyetzwc5f0
-  name: Pipeline in publicly accessible project
-  pipeline_template_uuid: zzzzz-p5p6p-tmpltpublicproj
-  state: Complete
-  created_at: <%= 30.minute.ago.to_fs(:db) %>
-  components:
-    foo:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: Collection
-          title: foo instance input
-      job:
-        uuid: zzzzz-8i9sb-jyq01m7in1jlofj
-        repository: active/foo
-        script: foo
-        script_version: main
-        script_parameters:
-          input: zzzzz-4zz18-4en62shvi99lxd4
-        log: zzzzz-4zz18-4en62shvi99lxd4
-        output: b519d9cb706a29fc7ea24dbea2f05851+93
-        state: Complete
-
-pipeline_in_publicly_accessible_project_but_other_objects_elsewhere:
-  uuid: zzzzz-d1hrv-pisharednotobjs
-  owner_uuid: zzzzz-j7d0g-zhxawtyetzwc5f0
-  name: Pipeline in public project with other objects elsewhere
-  pipeline_template_uuid: zzzzz-p5p6p-aox0k0ofxrystgw
-  state: Complete
-  created_at: 2014-09-20 12:00:00
-  components:
-    foo:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: Collection
-          title: foo instance input
-      job:
-        uuid: zzzzz-8i9sb-aceg2bnq7jt7kon
-        repository: active/foo
-        script: foo
-        script_version: main
-        script_parameters:
-          input: zzzzz-4zz18-bv31uwvy3neko21
-        log: zzzzz-4zz18-bv31uwvy3neko21
-        output: zzzzz-4zz18-bv31uwvy3neko21
-        state: Complete
-
-new_pipeline_in_publicly_accessible_project:
-  uuid: zzzzz-d1hrv-newpisharedobjs
-  owner_uuid: zzzzz-j7d0g-zhxawtyetzwc5f0
-  name: Pipeline in New state in publicly accessible project
-  pipeline_template_uuid: zzzzz-p5p6p-tmpltpublicproj
-  state: New
-  created_at: 2014-09-21 12:00:00
-  components:
-    foo:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: Collection
-          value: b519d9cb706a29fc7ea24dbea2f05851+93
-
-new_pipeline_in_publicly_accessible_project_but_other_objects_elsewhere:
-  uuid: zzzzz-d1hrv-newsharenotobjs
-  owner_uuid: zzzzz-j7d0g-zhxawtyetzwc5f0
-  name: Pipeline in New state in public project with objects elsewhere
-  pipeline_template_uuid: zzzzz-p5p6p-aox0k0ofxrystgw
-  state: New
-  created_at: 2014-09-22 12:00:00
-  components:
-    foo:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: Collection
-          value: zzzzz-4zz18-bv31uwvy3neko21
-
-new_pipeline_in_publicly_accessible_project_with_dataclass_file_and_other_objects_elsewhere:
-  uuid: zzzzz-d1hrv-newsharenotfile
-  owner_uuid: zzzzz-j7d0g-zhxawtyetzwc5f0
-  name: Pipeline in public project in New state with file type data class with objects elsewhere
-  pipeline_template_uuid: zzzzz-p5p6p-aox0k0ofxrystgw
-  state: New
-  created_at: 2014-09-23 12:00:00
-  components:
-    foo:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: File
-          value: zzzzz-4zz18-bv31uwvy3neko21/bar
-
-pipeline_in_running_state:
-  name: running_with_job
-  uuid: zzzzz-d1hrv-runningpipeline
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: <%= 2.8.minute.ago.to_fs(:db) %>
-  started_at: <%= 2.8.minute.ago.to_fs(:db) %>
-  state: RunningOnServer
-  components:
-   foo:
-    script: foo
-    script_version: main
-    script_parameters: {}
-    job:
-      uuid: zzzzz-8i9sb-pshmckwoma9plh7
-      script_version: main
-
-running_pipeline_with_complete_job:
-  uuid: zzzzz-d1hrv-partdonepipelin
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  state: RunningOnServer
-  created_at: <%= 15.minute.ago.to_fs(:db) %>
-  components:
-   previous:
-    job:
-      uuid: zzzzz-8i9sb-cjs4pklxxjykqqq
-      log: zzzzz-4zz18-op4e2lbej01tcvu
-   running:
-    job:
-      uuid: zzzzz-8i9sb-pshmckwoma9plh7
-
-complete_pipeline_with_two_jobs:
-  uuid: zzzzz-d1hrv-twodonepipeline
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  state: Complete
-  created_at: <%= 2.5.minute.ago.to_fs(:db) %>
-  started_at: <%= 2.minute.ago.to_fs(:db) %>
-  finished_at: <%= 1.minute.ago.to_fs(:db) %>
-  components:
-   ancient:
-    job:
-      uuid: zzzzz-8i9sb-ahd7cie8jah9qui
-      log: zzzzz-4zz18-op4e2lbej01tcvu
-   previous:
-    job:
-      uuid: zzzzz-8i9sb-cjs4pklxxjykqqq
-      log: zzzzz-4zz18-op4e2lbej01tcvu
-
-failed_pipeline_with_two_jobs:
-  uuid: zzzzz-d1hrv-twofailpipeline
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: <%= 55.minute.ago.to_fs(:db) %>
-  state: Failed
-  components:
-   ancient:
-    job:
-      uuid: zzzzz-8i9sb-ahd7cie8jah9qui
-      log: zzzzz-4zz18-op4e2lbej01tcvu
-   previous:
-    job:
-      uuid: zzzzz-8i9sb-cjs4pklxxjykqqq
-      log: zzzzz-4zz18-op4e2lbej01tcvu
-
-# This pipeline is a child of another running job and has it's own running children
-job_child_pipeline_with_components_at_level_2:
-  state: RunningOnServer
-  uuid: zzzzz-d1hrv-picomponentsl02
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: <%= 12.hour.ago.to_fs(:db) %>
-  started_at: <%= 12.hour.ago.to_fs(:db) %>
-  components:
-   foo:
-    script: foo
-    script_version: main
-    script_parameters: {}
-    job:
-      uuid: zzzzz-8i9sb-job1atlevel3noc
-      script_version: main
-      created_at: <%= 12.hour.ago.to_fs(:db) %>
-      started_at: <%= 12.hour.ago.to_fs(:db) %>
-      state: Running
-      tasks_summary:
-        failed: 0
-        todo: 0
-        running: 1
-        done: 1
-   bar:
-    script: bar
-    script_version: main
-    script_parameters: {}
-    job:
-      uuid: zzzzz-8i9sb-job2atlevel3noc
-      script_version: main
-      created_at: <%= 12.hour.ago.to_fs(:db) %>
-      started_at: <%= 12.hour.ago.to_fs(:db) %>
-      state: Running
-      tasks_summary:
-        failed: 0
-        todo: 1
-        running: 2
-        done: 3
-
-# Test Helper trims the rest of the file
-
-# Do not add your fixtures below this line as the rest of this file will be trimmed by test_helper
-
-# pipelines in project_with_10_pipelines
-<% for i in 1..10 do %>
-pipeline_<%=i%>_of_10:
-  name: pipeline_<%= i %>
-  uuid: zzzzz-d1hrv-10pipelines0<%= i.to_s.rjust(3, '0') %>
-  owner_uuid: zzzzz-j7d0g-000010pipelines
-  created_at: <%= (2*(i-1)).hour.ago.to_fs(:db) %>
-  started_at: <%= (2*(i-1)).hour.ago.to_fs(:db) %>
-  finished_at: <%= (i-1).minute.ago.to_fs(:db) %>
-  state: Failed
-  components:
-    foo:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: Collection
-          title: foo instance input
-      job:
-        state: Failed
-<% end %>
-
-# pipelines in project_with_2_pipelines_and_60_crs
-<% for i in 1..2 do %>
-pipeline_<%=i%>_of_2_pipelines_and_60_crs:
-  name: pipeline_<%= i %>
-  state: New
-  uuid: zzzzz-d1hrv-abcgneyn6brx<%= i.to_s.rjust(3, '0') %>
-  owner_uuid: zzzzz-j7d0g-nnncrspipelines
-  created_at: <%= i.minute.ago.to_fs(:db) %>
-  components:
-    foo:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: Collection
-          title: foo instance input
-<% end %>
-
-# pipelines in project_with_25_pipelines
-<% for i in 1..25 do %>
-pipeline_<%=i%>_of_25:
-  name: pipeline_<%=i%>
-  state: Failed
-  uuid: zzzzz-d1hrv-25pipelines0<%= i.to_s.rjust(3, '0') %>
-  owner_uuid: zzzzz-j7d0g-000025pipelines
-  created_at: <%= i.hour.ago.to_fs(:db) %>
-  started_at: <%= i.hour.ago.to_fs(:db) %>
-  finished_at: <%= i.minute.ago.to_fs(:db) %>
-  components:
-    foo:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: Collection
-          title: foo instance input
-<% end %>
-
-# Do not add your fixtures below this line as the rest of this file will be trimmed by test_helper
diff --git a/services/api/test/fixtures/pipeline_templates.yml b/services/api/test/fixtures/pipeline_templates.yml
deleted file mode 100644
index 0c185eeb80..0000000000
--- a/services/api/test/fixtures/pipeline_templates.yml
+++ /dev/null
@@ -1,271 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-two_part:
-  uuid: zzzzz-p5p6p-aox0k0ofxrystgw
-  owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
-  created_at: 2014-04-14 12:35:04 -0400
-  updated_at: 2014-04-14 12:35:04 -0400
-  modified_at: 2014-04-14 12:35:04 -0400
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  name: Two Part Pipeline Template
-  components:
-    part-one:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: Collection
-          title: "Foo/bar pair"
-    part-two:
-      script: bar
-      script_version: main
-      script_parameters:
-        input:
-          output_of: part-one
-        integer_with_default:
-          default: 123
-        integer_with_value:
-          value: 123
-        string_with_default:
-          default: baz
-        string_with_value:
-          value: baz
-        plain_string: qux
-        array_with_default: # important to test repeating values in the array!
-          default: [1,1,2,3,5]
-        array_with_value: # important to test repeating values in the array!
-          value: [1,1,2,3,5]
-
-components_is_jobspec:
-  # Helps test that clients cope with funny-shaped components.
-  # For an example, see #3321.
-  uuid: zzzzz-p5p6p-jobspeccomponts
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: 2014-04-14 12:35:04 -0400
-  updated_at: 2014-04-14 12:35:04 -0400
-  modified_at: 2014-04-14 12:35:04 -0400
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  name: Pipeline Template with Jobspec Components
-  components:
-    script: foo
-    script_version: main
-    script_parameters:
-      input:
-        required: true
-        dataclass: Collection
-        title: "Foo/bar pair"
-        description: "Provide a collection containing at least two files."
-
-parameter_with_search:
-  uuid: zzzzz-p5p6p-paramwsearch345
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: 2014-04-14 12:35:04 -0400
-  updated_at: 2014-04-14 12:35:04 -0400
-  modified_at: 2014-04-14 12:35:04 -0400
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  name: Pipeline Template with Input Parameter with Search
-  components:
-    with-search:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: Collection
-          title: "Foo/bar pair"
-          description: "Provide a collection containing at least two files."
-          search_for: sometime  # Matches baz_collection_in_asubproject
-
-new_pipeline_template:
-  # This template must include components that are not
-  # present in the pipeline instance 'pipeline_with_newer_template',
-  # at least one of which has a script_parameter that is a hash
-  # with a 'dataclass' field (ticket #4000)
-  uuid: zzzzz-p5p6p-vq4wuvy84xvaq2r
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: 2014-09-14 12:00:00
-  modified_at: 2014-09-16 12:00:00
-  name: Pipeline Template Newer Than Instance
-  components:
-    foo:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: Collection
-          title: foo template input
-    bar:
-      script: bar
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: Collection
-          title: bar template input
-
-pipeline_template_in_fuse_project:
-  uuid: zzzzz-p5p6p-templinfuseproj
-  owner_uuid: zzzzz-j7d0g-0000ownedbyfuse
-  created_at: 2014-04-14 12:35:04 -0400
-  updated_at: 2014-04-14 12:35:04 -0400
-  modified_at: 2014-04-14 12:35:04 -0400
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-0fusedrivertest
-  name: pipeline template in FUSE project
-  components:
-    foo_component:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: Collection
-          title: "default input"
-          description: "input collection"
-
-template_with_dataclass_file:
-  uuid: zzzzz-p5p6p-k0xoa0ofxrystgw
-  owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
-  created_at: 2014-04-14 12:35:04 -0400
-  updated_at: 2014-04-14 12:35:04 -0400
-  modified_at: 2014-04-14 12:35:04 -0400
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  name: Two Part Template with dataclass File
-  components:
-    part-one:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: File
-          title: "Foo/bar pair"
-          description: "Provide an input file"
-    part-two:
-      script: bar
-      script_version: main
-      script_parameters:
-        input:
-          output_of: part-one
-        integer_with_default:
-          default: 123
-        integer_with_value:
-          value: 123
-        string_with_default:
-          default: baz
-        string_with_value:
-          value: baz
-        plain_string: qux
-        array_with_default: # important to test repeating values in the array!
-          default: [1,1,2,3,5]
-        array_with_value: # important to test repeating values in the array!
-          value: [1,1,2,3,5]
-
-template_with_dataclass_number:
-  uuid: zzzzz-p5p6p-numbertemplatea
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: 2015-01-14 12:35:04 -0400
-  updated_at: 2015-01-14 12:35:04 -0400
-  modified_at: 2015-01-14 12:35:04 -0400
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  name: Template with dataclass number
-  components:
-    work:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: number
-          title: "Input number"
-
-pipeline_template_in_publicly_accessible_project:
-  uuid: zzzzz-p5p6p-tmpltpublicproj
-  owner_uuid: zzzzz-j7d0g-zhxawtyetzwc5f0
-  created_at: 2014-04-14 12:35:04 -0400
-  updated_at: 2014-04-14 12:35:04 -0400
-  modified_at: 2014-04-14 12:35:04 -0400
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  name: Pipeline template in publicly accessible project
-  components:
-    foo_component:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: Collection
-          title: "default input"
-          description: "input collection"
-
-# Used to test renaming when removed from the "aproject" subproject
-# while another such object with same name exists in home project.
-template_in_active_user_home_project_to_test_unique_key_violation:
-  uuid: zzzzz-p5p6p-templatsamenam1
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: 2013-04-14 12:35:04 -0400
-  updated_at: 2013-04-14 12:35:04 -0400
-  modified_at: 2013-04-14 12:35:04 -0400
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  name: Template to test owner uuid and name unique key violation upon removal
-  components:
-    script: foo
-    script_version: main
-    script_parameters:
-      input:
-        required: true
-        dataclass: Collection
-        title: "Foo/bar pair"
-        description: "Provide a collection containing at least two files."
-
-template_in_asubproject_with_same_name_as_one_in_active_user_home:
-  uuid: zzzzz-p5p6p-templatsamenam2
-  owner_uuid: zzzzz-j7d0g-axqo7eu9pwvna1x
-  created_at: 2013-04-14 12:35:04 -0400
-  updated_at: 2013-04-14 12:35:04 -0400
-  modified_at: 2013-04-14 12:35:04 -0400
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  name: Template to test owner uuid and name unique key violation upon removal
-  components:
-    script: foo
-    script_version: main
-    script_parameters:
-      input:
-        required: true
-        dataclass: Collection
-        title: "Foo/bar pair"
-        description: "Provide a collection containing at least two files."
-
-workflow_with_input_defaults:
-  uuid: zzzzz-p5p6p-aox0k0ofxrystg2
-  owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
-  created_at: 2014-04-14 12:35:04 -0400
-  updated_at: 2014-04-14 12:35:04 -0400
-  modified_at: 2014-04-14 12:35:04 -0400
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  name: Pipeline with default input specifications
-  components:
-    part-one:
-      script: foo
-      script_version: main
-      script_parameters:
-        ex_string:
-          required: true
-          dataclass: string
-        ex_string_def:
-          required: true
-          dataclass: string
-          default: hello-testing-123
diff --git a/services/api/test/fixtures/repositories.yml b/services/api/test/fixtures/repositories.yml
deleted file mode 100644
index e4fe71e402..0000000000
--- a/services/api/test/fixtures/repositories.yml
+++ /dev/null
@@ -1,52 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-crunch_dispatch_test:
-  uuid: zzzzz-s0uqq-382brsig8rp3665
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz # active user
-  name: active/crunchdispatchtest
-  created_at: 2015-01-01T00:00:00.123456Z
-  modified_at: 2015-01-01T00:00:00.123456Z
-
-arvados:
-  uuid: zzzzz-s0uqq-arvadosrepo0123
-  owner_uuid: zzzzz-tpzed-000000000000000 # root
-  name: arvados
-  created_at: 2015-01-01T00:00:00.123456Z
-  modified_at: 2015-01-01T00:00:00.123456Z
-
-foo:
-  uuid: zzzzz-s0uqq-382brsig8rp3666
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz # active user
-  name: active/foo
-  created_at: 2015-01-01T00:00:00.123456Z
-  modified_at: 2015-01-01T00:00:00.123456Z
-
-repository2:
-  uuid: zzzzz-s0uqq-382brsig8rp3667
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz # active user
-  name: active/foo2
-  created_at: 2015-01-01T00:00:00.123456Z
-  modified_at: 2015-01-01T00:00:00.123456Z
-
-repository3:
-  uuid: zzzzz-s0uqq-38orljkqpyo1j61
-  owner_uuid: zzzzz-tpzed-d9tiejq69daie8f # admin user
-  name: admin/foo3
-  created_at: 2015-01-01T00:00:00.123456Z
-  modified_at: 2015-01-01T00:00:00.123456Z
-
-repository4:
-  uuid: zzzzz-s0uqq-38oru8hnk57ht34
-  owner_uuid: zzzzz-tpzed-d9tiejq69daie8f # admin user
-  name: admin/foo4
-  created_at: 2015-01-01T00:00:00.123456Z
-  modified_at: 2015-01-01T00:00:00.123456Z
-
-has_branch_with_commit_hash_name:
-  uuid: zzzzz-s0uqq-382brsig8rp3668
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz # active user
-  name: active/shabranchnames
-  created_at: 2015-01-01T00:00:00.123456Z
-  modified_at: 2015-01-01T00:00:00.123456Z
diff --git a/services/api/test/fixtures/specimens.yml b/services/api/test/fixtures/specimens.yml
deleted file mode 100644
index bcae020812..0000000000
--- a/services/api/test/fixtures/specimens.yml
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-owned_by_active_user:
-  uuid: zzzzz-j58dm-3zx463qyo0k4xrn
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: 2014-04-21 15:37:48 -0400
-  modified_at: 2014-04-21 15:37:48 -0400
-
-owned_by_private_group:
-  uuid: zzzzz-j58dm-5m3qwg45g3nlpu6
-  owner_uuid: zzzzz-j7d0g-rew6elm53kancon
-  created_at: 2014-04-21 15:37:48 -0400
-  modified_at: 2014-04-21 15:37:48 -0400
-
-owned_by_spectator:
-  uuid: zzzzz-j58dm-3b0xxwzlbzxq5yr
-  owner_uuid: zzzzz-tpzed-l1s2piq4t4mps8r
-  created_at: 2014-04-21 15:37:48 -0400
-  modified_at: 2014-04-21 15:37:48 -0400
-
-in_aproject:
-  uuid: zzzzz-j58dm-7r18rnd5nzhg5yk
-  owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
-  created_at: 2014-04-21 15:37:48 -0400
-  modified_at: 2014-04-21 15:37:48 -0400
-
-in_asubproject:
-  uuid: zzzzz-j58dm-c40lddwcqqr1ffs
-  owner_uuid: zzzzz-j7d0g-axqo7eu9pwvna1x
-  created_at: 2014-04-21 15:37:48 -0400
-  modified_at: 2014-04-21 15:37:48 -0400
diff --git a/services/api/test/fixtures/traits.yml b/services/api/test/fixtures/traits.yml
deleted file mode 100644
index 83beb7087d..0000000000
--- a/services/api/test/fixtures/traits.yml
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-owned_by_aproject_with_no_name:
-  uuid: zzzzz-q1cn2-ypsjlol9dofwijz
-  owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
-  created_at: 2014-05-05 04:11:52 -0400
-  modified_at: 2014-05-05 04:11:52 -0400
diff --git a/services/api/test/functional/application_controller_test.rb b/services/api/test/functional/application_controller_test.rb
index af7882141e..2a64e9c5e2 100644
--- a/services/api/test/functional/application_controller_test.rb
+++ b/services/api/test/functional/application_controller_test.rb
@@ -13,8 +13,8 @@ class ApplicationControllerTest < ActionController::TestCase
 
   setup do
     # These tests are meant to check behavior in ApplicationController.
-    # We instantiate a small concrete controller for convenience.
-    @controller = Arvados::V1::SpecimensController.new
+    # We instantiate an arbitrary concrete controller.
+    @controller = Arvados::V1::CollectionsController.new
     @start_stamp = now_timestamp
   end
 
@@ -42,13 +42,13 @@ class ApplicationControllerTest < ActionController::TestCase
 
   test "requesting object without read permission returns 404 error" do
     authorize_with :spectator
-    get(:show, params: {id: specimens(:owned_by_active_user).uuid})
+    get(:show, params: {id: collections(:collection_owned_by_active).uuid})
     check_404
   end
 
   test "submitting bad object returns error" do
     authorize_with :spectator
-    post(:create, params: {specimen: {badattr: "badvalue"}})
+    post(:create, params: {collection: {badattr: "badvalue"}})
     assert_response 422
     check_error_token
   end
diff --git a/services/api/test/functional/arvados/v1/collections_controller_test.rb b/services/api/test/functional/arvados/v1/collections_controller_test.rb
index 43797035bc..3f65b934f5 100644
--- a/services/api/test/functional/arvados/v1/collections_controller_test.rb
+++ b/services/api/test/functional/arvados/v1/collections_controller_test.rb
@@ -516,14 +516,10 @@ EOS
 
   test "get full provenance for baz file" do
     authorize_with :active
-    get :provenance, params: {id: 'ea10d51bcf88862dbcc36eb292017dfd+45'}
+    get :provenance, params: {id: '1f4b0bc7583c2a7f9102c395f4ffc5e3+45'}
     assert_response :success
     resp = JSON.parse(@response.body)
-    assert_not_nil resp['ea10d51bcf88862dbcc36eb292017dfd+45'] # baz
-    assert_not_nil resp['fa7aeb5140e2848d39b416daeef4ffc5+45'] # bar
-    assert_not_nil resp['1f4b0bc7583c2a7f9102c395f4ffc5e3+45'] # foo
-    assert_not_nil resp['zzzzz-8i9sb-cjs4pklxxjykyuq'] # bar->baz
-    assert_not_nil resp['zzzzz-8i9sb-aceg2bnq7jt7kon'] # foo->bar
+    assert_not_nil resp['1f4b0bc7583c2a7f9102c395f4ffc5e3+45'] # baz collection
   end
 
   test "get no provenance for foo file" do
@@ -540,10 +536,7 @@ EOS
     assert_response :success
     resp = JSON.parse(@response.body)
     assert_not_nil resp['ea10d51bcf88862dbcc36eb292017dfd+45'] # baz
-    assert_not_nil resp['fa7aeb5140e2848d39b416daeef4ffc5+45'] # bar
-    assert_not_nil resp['zzzzz-8i9sb-cjs4pklxxjykyuq']     # bar->baz
-    assert_nil resp['zzzzz-8i9sb-aceg2bnq7jt7kon']         # foo->bar
-    assert_nil resp['1f4b0bc7583c2a7f9102c395f4ffc5e3+45'] # foo
+    assert_nil resp['fa7aeb5140e2848d39b416daeef4ffc5+45'] # foo->bar
   end
 
   test "search collections with 'any' operator" do
diff --git a/services/api/test/functional/arvados/v1/commits_controller_test.rb b/services/api/test/functional/arvados/v1/commits_controller_test.rb
deleted file mode 100644
index bf285b06e0..0000000000
--- a/services/api/test/functional/arvados/v1/commits_controller_test.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class Arvados::V1::CommitsControllerTest < ActionController::TestCase
-end
diff --git a/services/api/test/functional/arvados/v1/groups_controller_test.rb b/services/api/test/functional/arvados/v1/groups_controller_test.rb
index ee7f716c80..6e167bb91e 100644
--- a/services/api/test/functional/arvados/v1/groups_controller_test.rb
+++ b/services/api/test/functional/arvados/v1/groups_controller_test.rb
@@ -65,12 +65,12 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
     assert_equal 0, json_response['items_available']
   end
 
-  def check_project_contents_response disabled_kinds=[]
+  def check_project_contents_response
     assert_response :success
     assert_operator 2, :<=, json_response['items_available']
     assert_operator 2, :<=, json_response['items'].count
     kinds = json_response['items'].collect { |i| i['kind'] }.uniq
-    expect_kinds = %w'arvados#group arvados#specimen arvados#pipelineTemplate arvados#job' - disabled_kinds
+    expect_kinds = %w'arvados#group'
     assert_equal expect_kinds, (expect_kinds & kinds)
 
     json_response['items'].each do |i|
@@ -79,10 +79,6 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
                "group#contents returned a non-project group")
       end
     end
-
-    disabled_kinds.each do |d|
-      assert_equal true, !kinds.include?(d)
-    end
   end
 
   test 'get group-owned objects' do
@@ -107,17 +103,17 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
     authorize_with :project_viewer
     get :contents, params: {
       format: :json,
-      filters: [['uuid', 'is_a', 'arvados#specimen']]
+      filters: [['uuid', 'is_a', 'arvados#collection']]
     }
     assert_response :success
     found_uuids = json_response['items'].collect { |i| i['uuid'] }
-    [[:in_aproject, true],
-     [:in_asubproject, true],
-     [:owned_by_private_group, false]].each do |specimen_fixture, should_find|
+    [[:foo_collection_in_aproject, true],
+     [:baz_collection_name_in_asubproject, true],
+     [:collection_not_readable_by_active, false]].each do |collection_fixture, should_find|
       if should_find
-        assert_includes found_uuids, specimens(specimen_fixture).uuid, "did not find specimen fixture '#{specimen_fixture}'"
+        assert_includes found_uuids, collections(collection_fixture).uuid, "did not find collection fixture '#{collection_fixture}'"
       else
-        refute_includes found_uuids, specimens(specimen_fixture).uuid, "found specimen fixture '#{specimen_fixture}'"
+        refute_includes found_uuids, collections(collection_fixture).uuid, "found collection fixture '#{collection_fixture}'"
       end
     end
   end
@@ -150,8 +146,8 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
     }
     assert_response :success
     found_uuids = json_response['items'].collect { |i| i['uuid'] }
-    assert_includes found_uuids, specimens(:owned_by_active_user).uuid, "specimen did not appear in home project"
-    refute_includes found_uuids, specimens(:in_asubproject).uuid, "specimen appeared unexpectedly in home project"
+    assert_includes found_uuids, collections(:collection_owned_by_active).uuid, "collection did not appear in home project"
+    refute_includes found_uuids, collections(:foo_collection_in_aproject).uuid, "collection appeared unexpectedly in home project"
   end
 
   test "list collections in home project" do
@@ -279,20 +275,20 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
 
   test "user with project read permission can't rename items in it" do
     authorize_with :project_viewer
-    @controller = Arvados::V1::LinksController.new
+    @controller = Arvados::V1::CollectionsController.new
     post :update, params: {
-      id: jobs(:running).uuid,
+      id: collections(:collection_to_search_for_in_aproject).uuid,
       name: "Denied test name",
     }
     assert_includes(403..404, response.status)
   end
 
   test "user with project read permission can't remove items from it" do
-    @controller = Arvados::V1::PipelineTemplatesController.new
+    @controller = Arvados::V1::CollectionsController.new
     authorize_with :project_viewer
     post :update, params: {
-      id: pipeline_templates(:two_part).uuid,
-      pipeline_template: {
+      id: collections(:collection_to_search_for_in_aproject).uuid,
+      collection: {
         owner_uuid: users(:project_viewer).uuid,
       }
     }
@@ -339,8 +335,8 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
       select: ["uuid", "storage_classes_desired"]
     }
     assert_response :success
-    assert_equal 17, json_response['items_available']
-    assert_equal 17, json_response['items'].count
+    assert_equal 6, json_response['items_available']
+    assert_equal 6, json_response['items'].count
     json_response['items'].each do |item|
       # Expect collections to have a storage_classes field, other items should not.
       if item["kind"] == "arvados#collection"
@@ -480,9 +476,9 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
 
   [
     [['owner_uuid', '!=', 'zzzzz-tpzed-xurymjxw79nv3jz'], 200,
-        'zzzzz-d1hrv-subprojpipeline', 'zzzzz-d1hrv-1xfj6xkicf2muk2'],
-    [["pipeline_instances.state", "not in", ["Complete", "Failed"]], 200,
-        'zzzzz-d1hrv-1xfj6xkicf2muk2', 'zzzzz-d1hrv-i3e77t9z5y8j9cc'],
+        'zzzzz-j7d0g-publicfavorites', 'zzzzz-xvhdp-cr4queuedcontnr'],
+    [["container_requests.state", "not in", ["Final"]], 200,
+        'zzzzz-xvhdp-cr4queuedcontnr', 'zzzzz-xvhdp-cr4completedctr'],
     [['container_requests.requesting_container_uuid', '=', nil], 200,
         'zzzzz-xvhdp-cr4queuedcontnr', 'zzzzz-xvhdp-cr4requestercn2'],
     [['container_requests.no_such_column', '=', nil], 422],
@@ -503,25 +499,17 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
     end
   end
 
-  test 'get contents with jobs and pipeline instances disabled' do
-    Rails.configuration.API.DisabledAPIs = ConfigLoader.to_OrderedOptions(
-      {'jobs.index'=>{}, 'pipeline_instances.index'=>{}})
-
-    authorize_with :active
-    get :contents, params: {
-      id: groups(:aproject).uuid,
-      format: :json,
-    }
-    check_project_contents_response %w'arvados#pipelineInstance arvados#job'
-  end
-
   test 'get contents with low max_index_database_read' do
     # Some result will certainly have at least 12 bytes in a
-    # restricted column
+    # restricted column.
+    #
+    # We cannot use collections.manifest_text to test this, because
+    # GroupsController refuses to select manifest_text, because
+    # controller doesn't sign manifests in a groups#contents response.
     Rails.configuration.API.MaxIndexDatabaseRead = 12
     authorize_with :active
     get :contents, params: {
-          id: groups(:aproject).uuid,
+          uuid: users(:active).uuid,
           format: :json,
         }
     assert_response :success
diff --git a/services/api/test/functional/arvados/v1/humans_controller_test.rb b/services/api/test/functional/arvados/v1/humans_controller_test.rb
deleted file mode 100644
index d73fb30513..0000000000
--- a/services/api/test/functional/arvados/v1/humans_controller_test.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class Arvados::V1::HumansControllerTest < ActionController::TestCase
-end
diff --git a/services/api/test/functional/arvados/v1/job_reuse_controller_test.rb b/services/api/test/functional/arvados/v1/job_reuse_controller_test.rb
deleted file mode 100644
index 46cfac5c9a..0000000000
--- a/services/api/test/functional/arvados/v1/job_reuse_controller_test.rb
+++ /dev/null
@@ -1,106 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-require 'helpers/git_test_helper'
-
-class Arvados::V1::JobReuseControllerTest < ActionController::TestCase
-  fixtures :repositories, :users, :jobs, :links, :collections
-
-  setup do
-    @controller = Arvados::V1::JobsController.new
-    authorize_with :active
-  end
-
-  BASE_FILTERS = {
-    'repository' => ['=', 'active/foo'],
-    'script' => ['=', 'hash'],
-    'script_version' => ['in git', 'main'],
-    'docker_image_locator' => ['=', nil],
-    'arvados_sdk_version' => ['=', nil],
-  }
-
-  def filters_from_hash(hash)
-    hash.each_pair.map { |name, filter| [name] + filter }
-  end
-
-  test "find Job with script version range" do
-    get :index, params: {
-      filters: [["repository", "=", "active/foo"],
-                ["script", "=", "hash"],
-                ["script_version", "in git", "tag1"]]
-    }
-    assert_response :success
-    assert_not_nil assigns(:objects)
-    assert_includes(assigns(:objects).map { |job| job.uuid },
-                    jobs(:previous_job_run).uuid)
-  end
-
-  test "find Job with script version range exclusions" do
-    get :index, params: {
-      filters: [["repository", "=", "active/foo"],
-                ["script", "=", "hash"],
-                ["script_version", "not in git", "tag1"]]
-    }
-    assert_response :success
-    assert_not_nil assigns(:objects)
-    refute_includes(assigns(:objects).map { |job| job.uuid },
-                    jobs(:previous_job_run).uuid)
-  end
-
-  test "find Job with Docker image range" do
-    get :index, params: {
-      filters: [["docker_image_locator", "in docker",
-                 "arvados/apitestfixture"]]
-    }
-    assert_response :success
-    assert_not_nil assigns(:objects)
-    assert_includes(assigns(:objects).map { |job| job.uuid },
-                    jobs(:previous_docker_job_run).uuid)
-    refute_includes(assigns(:objects).map { |job| job.uuid },
-                    jobs(:previous_job_run).uuid)
-  end
-
-  test "find Job with Docker image using reader tokens" do
-    authorize_with :inactive
-    get(:index, params: {
-          filters: [["docker_image_locator", "in docker",
-                     "arvados/apitestfixture"]],
-          reader_tokens: [api_token(:active)],
-        })
-    assert_response :success
-    assert_not_nil assigns(:objects)
-    assert_includes(assigns(:objects).map { |job| job.uuid },
-                    jobs(:previous_docker_job_run).uuid)
-    refute_includes(assigns(:objects).map { |job| job.uuid },
-                    jobs(:previous_job_run).uuid)
-  end
-
-  test "'in docker' filter accepts arrays" do
-    get :index, params: {
-      filters: [["docker_image_locator", "in docker",
-                ["_nonesuchname_", "arvados/apitestfixture"]]]
-    }
-    assert_response :success
-    assert_not_nil assigns(:objects)
-    assert_includes(assigns(:objects).map { |job| job.uuid },
-                    jobs(:previous_docker_job_run).uuid)
-    refute_includes(assigns(:objects).map { |job| job.uuid },
-                    jobs(:previous_job_run).uuid)
-  end
-
-  test "'not in docker' filter accepts arrays" do
-    get :index, params: {
-      filters: [["docker_image_locator", "not in docker",
-                ["_nonesuchname_", "arvados/apitestfixture"]]]
-    }
-    assert_response :success
-    assert_not_nil assigns(:objects)
-    assert_includes(assigns(:objects).map { |job| job.uuid },
-                    jobs(:previous_job_run).uuid)
-    refute_includes(assigns(:objects).map { |job| job.uuid },
-                    jobs(:previous_docker_job_run).uuid)
-  end
-
-end
diff --git a/services/api/test/functional/arvados/v1/job_tasks_controller_test.rb b/services/api/test/functional/arvados/v1/job_tasks_controller_test.rb
deleted file mode 100644
index d6f4347b87..0000000000
--- a/services/api/test/functional/arvados/v1/job_tasks_controller_test.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class Arvados::V1::JobTasksControllerTest < ActionController::TestCase
-end
diff --git a/services/api/test/functional/arvados/v1/jobs_controller_test.rb b/services/api/test/functional/arvados/v1/jobs_controller_test.rb
deleted file mode 100644
index 9298f23d54..0000000000
--- a/services/api/test/functional/arvados/v1/jobs_controller_test.rb
+++ /dev/null
@@ -1,191 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-require 'helpers/git_test_helper'
-
-class Arvados::V1::JobsControllerTest < ActionController::TestCase
-
-  test "search jobs by uuid with >= query" do
-    authorize_with :active
-    get :index, params: {
-      filters: [['uuid', '>=', 'zzzzz-8i9sb-pshmckwoma9plh7']]
-    }
-    assert_response :success
-    found = assigns(:objects).collect(&:uuid)
-    assert_equal true, !!found.index('zzzzz-8i9sb-pshmckwoma9plh7')
-    assert_equal false, !!found.index('zzzzz-8i9sb-4cf0nhn6xte809j')
-  end
-
-  test "search jobs by uuid with <= query" do
-    authorize_with :active
-    get :index, params: {
-      filters: [['uuid', '<=', 'zzzzz-8i9sb-pshmckwoma9plh7']]
-    }
-    assert_response :success
-    found = assigns(:objects).collect(&:uuid)
-    assert_equal true, !!found.index('zzzzz-8i9sb-pshmckwoma9plh7')
-    assert_equal true, !!found.index('zzzzz-8i9sb-4cf0nhn6xte809j')
-  end
-
-  test "search jobs by uuid with >= and <= query" do
-    authorize_with :active
-    get :index, params: {
-      filters: [['uuid', '>=', 'zzzzz-8i9sb-pshmckwoma9plh7'],
-              ['uuid', '<=', 'zzzzz-8i9sb-pshmckwoma9plh7']]
-    }
-    assert_response :success
-    found = assigns(:objects).collect(&:uuid)
-    assert_equal found, ['zzzzz-8i9sb-pshmckwoma9plh7']
-  end
-
-  test "search jobs by uuid with < query" do
-    authorize_with :active
-    get :index, params: {
-      filters: [['uuid', '<', 'zzzzz-8i9sb-pshmckwoma9plh7']]
-    }
-    assert_response :success
-    found = assigns(:objects).collect(&:uuid)
-    assert_equal false, !!found.index('zzzzz-8i9sb-pshmckwoma9plh7')
-    assert_equal true, !!found.index('zzzzz-8i9sb-4cf0nhn6xte809j')
-  end
-
-  test "search jobs by uuid with like query" do
-    authorize_with :active
-    get :index, params: {
-      filters: [['uuid', 'like', '%hmckwoma9pl%']]
-    }
-    assert_response :success
-    found = assigns(:objects).collect(&:uuid)
-    assert_equal found, ['zzzzz-8i9sb-pshmckwoma9plh7']
-  end
-
-  test "search jobs by uuid with 'in' query" do
-    authorize_with :active
-    get :index, params: {
-      filters: [['uuid', 'in', ['zzzzz-8i9sb-4cf0nhn6xte809j',
-                                'zzzzz-8i9sb-pshmckwoma9plh7']]]
-    }
-    assert_response :success
-    found = assigns(:objects).collect(&:uuid)
-    assert_equal found.sort, ['zzzzz-8i9sb-4cf0nhn6xte809j',
-                              'zzzzz-8i9sb-pshmckwoma9plh7']
-  end
-
-  test "search jobs by uuid with 'not in' query" do
-    exclude_uuids = [jobs(:running).uuid,
-                     jobs(:running_cancelled).uuid]
-    authorize_with :active
-    get :index, params: {
-      filters: [['uuid', 'not in', exclude_uuids]]
-    }
-    assert_response :success
-    found = assigns(:objects).collect(&:uuid)
-    assert_not_empty found, "'not in' query returned nothing"
-    assert_empty(found & exclude_uuids,
-                 "'not in' query returned uuids I asked not to get")
-  end
-
-  ['=', '!='].each do |operator|
-    [['uuid', 'zzzzz-8i9sb-pshmckwoma9plh7'],
-     ['output', nil]].each do |attr, operand|
-      test "search jobs with #{attr} #{operator} #{operand.inspect} query" do
-        authorize_with :active
-        get :index, params: {
-          filters: [[attr, operator, operand]]
-        }
-        assert_response :success
-        values = assigns(:objects).collect { |x| x.send(attr) }
-        assert_not_empty values, "query should return non-empty result"
-        if operator == '='
-          assert_empty values - [operand], "query results do not satisfy query"
-        else
-          assert_empty values & [operand], "query results do not satisfy query"
-        end
-      end
-    end
-  end
-
-  test "search jobs by started_at with < query" do
-    authorize_with :active
-    get :index, params: {
-      filters: [['started_at', '<', Time.now.to_s]]
-    }
-    assert_response :success
-    found = assigns(:objects).collect(&:uuid)
-    assert_equal true, !!found.index('zzzzz-8i9sb-pshmckwoma9plh7')
-  end
-
-  test "search jobs by started_at with > query" do
-    authorize_with :active
-    get :index, params: {
-      filters: [['started_at', '>', Time.now.to_s]]
-    }
-    assert_response :success
-    assert_equal 0, assigns(:objects).count
-  end
-
-  test "search jobs by started_at with >= query on metric date" do
-    authorize_with :active
-    get :index, params: {
-      filters: [['started_at', '>=', '2014-01-01']]
-    }
-    assert_response :success
-    found = assigns(:objects).collect(&:uuid)
-    assert_equal true, !!found.index('zzzzz-8i9sb-pshmckwoma9plh7')
-  end
-
-  test "search jobs by started_at with >= query on metric date and time" do
-    authorize_with :active
-    get :index, params: {
-      filters: [['started_at', '>=', '2014-01-01 01:23:45']]
-    }
-    assert_response :success
-    found = assigns(:objects).collect(&:uuid)
-    assert_equal true, !!found.index('zzzzz-8i9sb-pshmckwoma9plh7')
-  end
-
-  test "search jobs with 'any' operator" do
-    authorize_with :active
-    get :index, params: {
-      where: { any: ['contains', 'pshmckw'] }
-    }
-    assert_response :success
-    found = assigns(:objects).collect(&:uuid)
-    assert_equal 0, found.index('zzzzz-8i9sb-pshmckwoma9plh7')
-    assert_equal 1, found.count
-  end
-
-  test "search jobs by nonexistent column with < query" do
-    authorize_with :active
-    get :index, params: {
-      filters: [['is_borked', '<', 'fizzbuzz']]
-    }
-    assert_response 422
-  end
-
-  [:spectator, :admin].each_with_index do |which_token, i|
-    test "get job queue as #{which_token} user" do
-      authorize_with which_token
-      get :queue
-      assert_response :success
-      assert_equal 0, assigns(:objects).count
-    end
-  end
-
-  test "job includes assigned nodes" do
-    authorize_with :active
-    get :show, params: {id: jobs(:nearly_finished_job).uuid}
-    assert_response :success
-    assert_equal([nodes(:busy).uuid], json_response["node_uuids"])
-  end
-
-  test 'get job with components' do
-    authorize_with :active
-    get :show, params: {id: jobs(:running_job_with_components).uuid}
-    assert_response :success
-    assert_not_nil json_response["components"]
-    assert_equal ["component1", "component2"], json_response["components"].keys
-  end
-end
diff --git a/services/api/test/functional/arvados/v1/keep_disks_controller_test.rb b/services/api/test/functional/arvados/v1/keep_disks_controller_test.rb
deleted file mode 100644
index 9da9d01631..0000000000
--- a/services/api/test/functional/arvados/v1/keep_disks_controller_test.rb
+++ /dev/null
@@ -1,102 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class Arvados::V1::KeepDisksControllerTest < ActionController::TestCase
-
-  def default_ping_opts
-    {ping_secret: '', service_ssl_flag: false, service_port: 1234}
-  end
-
-  test "add keep disk with admin token" do
-    authorize_with :admin
-    post :ping, params: default_ping_opts.
-      merge(filesystem_uuid: 'eb1e77a1-db84-4193-b6e6-ca2894f67d5f')
-    assert_response :success
-    assert_not_nil assigns(:object)
-    new_keep_disk = JSON.parse(@response.body)
-    assert_not_nil new_keep_disk['uuid']
-    assert_not_nil new_keep_disk['ping_secret']
-    assert_not_equal '', new_keep_disk['ping_secret']
-  end
-
-  [
-    {},
-    {filesystem_uuid: ''},
-  ].each do |opts|
-    test "add keep disk with[out] filesystem_uuid #{opts}" do
-      authorize_with :admin
-      post :ping, params: default_ping_opts.merge(opts)
-      assert_response :success
-      assert_not_nil JSON.parse(@response.body)['uuid']
-    end
-  end
-
-  test "refuse to add keep disk without admin token" do
-    post :ping, params: default_ping_opts
-    assert_response 404
-  end
-
-  test "ping keep disk" do
-    post :ping, params: default_ping_opts.
-      merge(id: keep_disks(:nonfull).uuid,
-            ping_secret: keep_disks(:nonfull).ping_secret,
-            filesystem_uuid: keep_disks(:nonfull).filesystem_uuid)
-    assert_response :success
-    assert_not_nil assigns(:object)
-    keep_disk = JSON.parse(@response.body)
-    assert_not_nil keep_disk['uuid']
-    assert_not_nil keep_disk['ping_secret']
-  end
-
-  test "admin should get index with ping_secret" do
-    authorize_with :admin
-    get :index
-    assert_response :success
-    assert_not_nil assigns(:objects)
-    items = JSON.parse(@response.body)['items']
-    assert_not_equal 0, items.size
-    assert_not_nil items[0]['ping_secret']
-  end
-
-  # inactive user sees keep disks
-  test "inactive user should get index" do
-    authorize_with :inactive
-    get :index
-    assert_response :success
-    items = JSON.parse(@response.body)['items']
-    assert_not_equal 0, items.size
-
-    # Check these are still included
-    assert items[0]['service_host']
-    assert items[0]['service_port']
-  end
-
-  # active user sees non-secret attributes of keep disks
-  test "active user should get non-empty index with no ping_secret" do
-    authorize_with :active
-    get :index
-    assert_response :success
-    items = JSON.parse(@response.body)['items']
-    assert_not_equal 0, items.size
-    items.each do |item|
-      assert_nil item['ping_secret']
-      assert_not_nil item['is_readable']
-      assert_not_nil item['is_writable']
-      assert_not_nil item['service_host']
-      assert_not_nil item['service_port']
-    end
-  end
-
-  test "search keep_services with 'any' operator" do
-    authorize_with :active
-    get :index, params: {
-      where: { any: ['contains', 'o2t1q5w'] }
-    }
-    assert_response :success
-    found = assigns(:objects).collect(&:uuid)
-    assert_equal true, !!found.index('zzzzz-penuu-5w2o2t1q5wy7fhn')
-  end
-end
diff --git a/services/api/test/functional/arvados/v1/nodes_controller_test.rb b/services/api/test/functional/arvados/v1/nodes_controller_test.rb
deleted file mode 100644
index 47f6c5ff3f..0000000000
--- a/services/api/test/functional/arvados/v1/nodes_controller_test.rb
+++ /dev/null
@@ -1,260 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class Arvados::V1::NodesControllerTest < ActionController::TestCase
-
-  test "should get index with ping_secret" do
-    authorize_with :admin
-    get :index
-    assert_response :success
-    assert_not_nil assigns(:objects)
-    node_items = JSON.parse(@response.body)['items']
-    assert_not_equal 0, node_items.size
-    assert_not_nil node_items[0]['info'].andand['ping_secret']
-  end
-
-  # inactive user does not see any nodes
-  test "inactive user should get empty index" do
-    authorize_with :inactive
-    get :index
-    assert_response :success
-    assert_equal 0, json_response['items'].size
-    assert_equal 0, json_response['items_available']
-  end
-
-  # active user sees non-secret attributes of up and recently-up nodes
-  test "active user should get non-empty index with no ping_secret" do
-    authorize_with :active
-    get :index
-    assert_response :success
-    assert_operator 0, :<, json_response['items_available']
-    node_items = json_response['items']
-    assert_operator 0, :<, node_items.size
-    found_busy_node = false
-    node_items.each do |node|
-      assert_nil node['info'].andand['ping_secret']
-      assert_not_nil node['crunch_worker_state']
-      if node['uuid'] == nodes(:busy).uuid
-        found_busy_node = true
-        assert_equal 'busy', node['crunch_worker_state']
-      end
-    end
-    assert_equal true, found_busy_node
-  end
-
-  test "node should ping with ping_secret and no token" do
-    post :ping, params: {
-      id: 'zzzzz-7ekkf-2z3mc76g2q73aio',
-      instance_id: 'i-0000000',
-      local_ipv4: '172.17.2.174',
-      ping_secret: '69udawxvn3zzj45hs8bumvndricrha4lcpi23pd69e44soanc0'
-    }
-    assert_response :success
-    response = JSON.parse(@response.body)
-    assert_equal 'zzzzz-7ekkf-2z3mc76g2q73aio', response['uuid']
-    # Ensure we are getting the "superuser" attributes, too
-    assert_not_nil response['first_ping_at'], '"first_ping_at" attr missing'
-    assert_not_nil response['info'], '"info" attr missing'
-    assert_not_nil response['nameservers'], '"nameservers" attr missing'
-  end
-
-  test "node should fail ping with invalid ping_secret" do
-    post :ping, params: {
-      id: 'zzzzz-7ekkf-2z3mc76g2q73aio',
-      instance_id: 'i-0000000',
-      local_ipv4: '172.17.2.174',
-      ping_secret: 'dricrha4lcpi23pd69e44soanc069udawxvn3zzj45hs8bumvn'
-    }
-    assert_response 401
-  end
-
-  test "create node" do
-    authorize_with :admin
-    post :create, params: {node: {}}
-    assert_response :success
-    assert_not_nil json_response['uuid']
-    assert_not_nil json_response['info'].is_a? Hash
-    assert_not_nil json_response['info']['ping_secret']
-    assert_nil json_response['slot_number']
-    assert_nil json_response['hostname']
-  end
-
-  test "create node and assign slot" do
-    authorize_with :admin
-    post :create, params: {node: {}, assign_slot: true}
-    assert_response :success
-    assert_not_nil json_response['uuid']
-    assert_not_nil json_response['info'].is_a? Hash
-    assert_not_nil json_response['info']['ping_secret']
-    assert_operator 0, :<, json_response['slot_number']
-    n = json_response['slot_number']
-    assert_equal "compute#{n}", json_response['hostname']
-
-    node = Node.where(uuid: json_response['uuid']).first
-    assert_equal n, node.slot_number
-    assert_equal "compute#{n}", node.hostname
-  end
-
-  test "update node and assign slot" do
-    authorize_with :admin
-    node = nodes(:new_with_no_hostname)
-    post :update, params: {id: node.uuid, node: {}, assign_slot: true}
-    assert_response :success
-    assert_operator 0, :<, json_response['slot_number']
-    n = json_response['slot_number']
-    assert_equal "compute#{n}", json_response['hostname']
-
-    node.reload
-    assert_equal n, node.slot_number
-    assert_equal "compute#{n}", node.hostname
-  end
-
-  test "update node and assign slot, don't clobber hostname" do
-    authorize_with :admin
-    node = nodes(:new_with_custom_hostname)
-    post :update, params: {id: node.uuid, node: {}, assign_slot: true}
-    assert_response :success
-    assert_operator 0, :<, json_response['slot_number']
-    n = json_response['slot_number']
-    assert_equal "custom1", json_response['hostname']
-  end
-
-  test "ping adds node stats to info" do
-    authorize_with :admin
-    node = nodes(:idle)
-    post :ping, params: {
-      id: node.uuid,
-      ping_secret: node.info['ping_secret'],
-      total_cpu_cores: 32,
-      total_ram_mb: 1024,
-      total_scratch_mb: 2048
-    }
-    assert_response :success
-    info = JSON.parse(@response.body)['info']
-    properties = JSON.parse(@response.body)['properties']
-    assert_equal(node.info['ping_secret'], info['ping_secret'])
-    assert_equal(32, properties['total_cpu_cores'].to_i)
-    assert_equal(1024, properties['total_ram_mb'].to_i)
-    assert_equal(2048, properties['total_scratch_mb'].to_i)
-  end
-
-  test "active user can see their assigned job" do
-    authorize_with :active
-    get :show, params: {id: nodes(:busy).uuid}
-    assert_response :success
-    assert_equal(jobs(:nearly_finished_job).uuid, json_response["job_uuid"])
-  end
-
-  test "user without job read permission can't see job" do
-    authorize_with :spectator
-    get :show, params: {id: nodes(:busy).uuid}
-    assert_response :success
-    assert_nil(json_response["job"], "spectator can see node's assigned job")
-  end
-
-  [:admin, :spectator].each do |user|
-    test "select param does not break node list for #{user}" do
-      authorize_with user
-      get :index, params: {select: ['domain']}
-      assert_response :success
-      assert_operator 0, :<, json_response['items_available']
-    end
-  end
-
-  test "admin can associate a job with a node" do
-    changed_node = nodes(:idle)
-    assigned_job = jobs(:queued)
-    authorize_with :admin
-    post :update, params: {
-      id: changed_node.uuid,
-      node: {job_uuid: assigned_job.uuid},
-    }
-    assert_response :success
-    assert_equal(changed_node.hostname, json_response["hostname"],
-                 "hostname mismatch after defining job")
-    assert_equal(assigned_job.uuid, json_response["job_uuid"],
-                 "mismatch in node's assigned job UUID")
-  end
-
-  test "non-admin can't associate a job with a node" do
-    authorize_with :active
-    post :update, params: {
-      id: nodes(:idle).uuid,
-      node: {job_uuid: jobs(:queued).uuid},
-    }
-    assert_response 403
-  end
-
-  test "admin can unassign a job from a node" do
-    changed_node = nodes(:busy)
-    authorize_with :admin
-    post :update, params: {
-      id: changed_node.uuid,
-      node: {job_uuid: nil},
-    }
-    assert_response :success
-    assert_equal(changed_node.hostname, json_response["hostname"],
-                 "hostname mismatch after defining job")
-    assert_nil(json_response["job_uuid"],
-               "node still has job assignment after update")
-  end
-
-  test "non-admin can't unassign a job from a node" do
-    authorize_with :project_viewer
-    post :update, params: {
-      id: nodes(:busy).uuid,
-      node: {job_uuid: nil},
-    }
-    assert_response 403
-  end
-
-  test "node should fail ping with invalid hostname config format" do
-    Rails.configuration.Containers.SLURM.Managed.AssignNodeHostname = 'compute%<slot_number>04'  # should end with "04d"
-    post :ping, params: {
-      id: nodes(:new_with_no_hostname).uuid,
-      ping_secret: nodes(:new_with_no_hostname).info['ping_secret'],
-    }
-    assert_response 422
-  end
-
-  test "first ping should set ip addr using local_ipv4 when provided" do
-    post :ping, params: {
-      id: 'zzzzz-7ekkf-nodenoipaddryet',
-      instance_id: 'i-0000000',
-      local_ipv4: '172.17.2.172',
-      ping_secret: 'abcdyefg4lb5q4gzqqtrnq30oyj08r8dtdimmanbqw49z1anz2'
-    }
-    assert_response :success
-    response = JSON.parse(@response.body)
-    assert_equal 'zzzzz-7ekkf-nodenoipaddryet', response['uuid']
-    assert_equal '172.17.2.172', response['ip_address']
-  end
-
-  test "first ping should set ip addr using remote_ip when local_ipv4 is not provided" do
-    post :ping, params: {
-      id: 'zzzzz-7ekkf-nodenoipaddryet',
-      instance_id: 'i-0000000',
-      ping_secret: 'abcdyefg4lb5q4gzqqtrnq30oyj08r8dtdimmanbqw49z1anz2'
-    }
-    assert_response :success
-    response = JSON.parse(@response.body)
-    assert_equal 'zzzzz-7ekkf-nodenoipaddryet', response['uuid']
-    assert_equal request.remote_ip, response['ip_address']
-  end
-
-  test "future pings should not change previous ip address" do
-    post :ping, params: {
-      id: 'zzzzz-7ekkf-2z3mc76g2q73aio',
-      instance_id: 'i-0000000',
-      local_ipv4: '172.17.2.175',
-      ping_secret: '69udawxvn3zzj45hs8bumvndricrha4lcpi23pd69e44soanc0'
-    }
-    assert_response :success
-    response = JSON.parse(@response.body)
-    assert_equal 'zzzzz-7ekkf-2z3mc76g2q73aio', response['uuid']
-    assert_equal '172.17.2.174', response['ip_address']   # original ip address is not overwritten
-  end
-end
diff --git a/services/api/test/functional/arvados/v1/pipeline_instances_controller_test.rb b/services/api/test/functional/arvados/v1/pipeline_instances_controller_test.rb
deleted file mode 100644
index e455354c11..0000000000
--- a/services/api/test/functional/arvados/v1/pipeline_instances_controller_test.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class Arvados::V1::PipelineInstancesControllerTest < ActionController::TestCase
-end
diff --git a/services/api/test/functional/arvados/v1/pipeline_templates_controller_test.rb b/services/api/test/functional/arvados/v1/pipeline_templates_controller_test.rb
deleted file mode 100644
index 992749c6f1..0000000000
--- a/services/api/test/functional/arvados/v1/pipeline_templates_controller_test.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class Arvados::V1::PipelineTemplatesControllerTest < ActionController::TestCase
-end
diff --git a/services/api/test/functional/arvados/v1/repositories_controller_test.rb b/services/api/test/functional/arvados/v1/repositories_controller_test.rb
deleted file mode 100644
index 84bd846c91..0000000000
--- a/services/api/test/functional/arvados/v1/repositories_controller_test.rb
+++ /dev/null
@@ -1,246 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class Arvados::V1::RepositoriesControllerTest < ActionController::TestCase
-  test "should get_all_logins with admin token" do
-    authorize_with :admin
-    get :get_all_permissions
-    assert_response :success
-  end
-
-  test "should get_all_logins with non-admin token" do
-    authorize_with :active
-    get :get_all_permissions
-    assert_response 403
-  end
-
-  test "get_all_permissions gives RW to repository owner" do
-    authorize_with :admin
-    get :get_all_permissions
-    assert_response :success
-    ok = false
-    json_response['repositories'].each do |repo|
-      if repo['uuid'] == repositories(:repository2).uuid
-        if repo['user_permissions'][users(:active).uuid]['can_write']
-          ok = true
-        end
-      end
-    end
-    assert_equal(true, ok,
-                 "No permission on own repo '@{repositories(:repository2).uuid}'")
-  end
-
-  test "get_all_permissions takes into account is_admin flag" do
-    authorize_with :admin
-    get :get_all_permissions
-    assert_response :success
-    json_response['repositories'].each do |repo|
-      assert_not_nil(repo['user_permissions'][users(:admin).uuid],
-                     "Admin user is not listed in perms for #{repo['uuid']}")
-      assert_equal(true,
-                   repo['user_permissions'][users(:admin).uuid]['can_write'],
-                   "Admin has no perms for #{repo['uuid']}")
-    end
-  end
-
-  test "get_all_permissions takes into account is_active flag" do
-    act_as_user users(:active) do
-      Repository.create! name: 'active/testrepo'
-    end
-    act_as_system_user do
-      u = users(:active)
-      u.unsetup
-      u.save!
-    end
-    authorize_with :admin
-    get :get_all_permissions
-    assert_response :success
-    json_response['repositories'].each do |r|
-      r['user_permissions'].each do |user_uuid, perms|
-        refute_equal user_uuid, users(:active).uuid
-      end
-    end
-  end
-
-  test "get_all_permissions does not give any access to user without permission" do
-    viewer_uuid = users(:project_viewer).uuid
-    assert_equal(authorized_keys(:project_viewer).authorized_user_uuid,
-                 viewer_uuid,
-                 "project_viewer must have an authorized_key for this test to work")
-    authorize_with :admin
-    get :get_all_permissions
-    assert_response :success
-    readable_repos = json_response["repositories"].select do |repo|
-      repo["user_permissions"].has_key?(viewer_uuid)
-    end
-    assert_equal(["arvados"], readable_repos.map { |r| r["name"] },
-                 "project_viewer should only have permissions on public repos")
-  end
-
-  test "get_all_permissions gives gitolite R to user with read-only access" do
-    authorize_with :admin
-    get :get_all_permissions
-    assert_response :success
-    found_it = false
-    assert_equal(authorized_keys(:spectator).authorized_user_uuid,
-                 users(:spectator).uuid,
-                 "spectator must have an authorized_key for this test to work")
-    json_response['repositories'].each do |repo|
-      next unless repo['uuid'] == repositories(:foo).uuid
-      assert_equal('R',
-                   repo['user_permissions'][users(:spectator).uuid]['gitolite_permissions'],
-                   "spectator user should have just R access to #{repo['uuid']}")
-      found_it = true
-    end
-    assert_equal true, found_it, "spectator user does not have R on foo repo"
-  end
-
-  test "get_all_permissions provides admin and active user keys" do
-    authorize_with :admin
-    get :get_all_permissions
-    assert_response :success
-    [:active, :admin].each do |u|
-      assert_equal(1, json_response['user_keys'][users(u).uuid].andand.count,
-                   "expected 1 key for #{u} (#{users(u).uuid})")
-      assert_equal(json_response['user_keys'][users(u).uuid][0]['public_key'],
-                   authorized_keys(u).public_key,
-                   "response public_key does not match fixture #{u}.")
-    end
-  end
-
-  test "get_all_permissions lists all repos regardless of permissions" do
-    act_as_system_user do
-      # Create repos that could potentially be left out of the
-      # permission list by accident.
-
-      # No authorized_key, no username (this can't even be done
-      # without skipping validations)
-      r = Repository.create name: 'root/testrepo'
-      assert r.save validate: false
-
-      r = Repository.create name: 'invalid username / repo name', owner_uuid: users(:inactive).uuid
-      assert r.save validate: false
-    end
-    authorize_with :admin
-    get :get_all_permissions
-    assert_response :success
-    assert_equal(Repository.count, json_response["repositories"].size)
-  end
-
-  test "get_all_permissions lists user permissions for users with no authorized keys" do
-    authorize_with :admin
-    AuthorizedKey.destroy_all
-    get :get_all_permissions
-    assert_response :success
-    assert_equal(Repository.count, json_response["repositories"].size)
-    repos_with_perms = []
-    json_response['repositories'].each do |repo|
-      if repo['user_permissions'].any?
-        repos_with_perms << repo['uuid']
-      end
-    end
-    assert_not_empty repos_with_perms, 'permissions are missing'
-  end
-
-  # Ensure get_all_permissions correctly describes what the normal
-  # permission system would do.
-  test "get_all_permissions obeys group permissions" do
-    act_as_user system_user do
-      r = Repository.create!(name: 'admin/groupcanwrite', owner_uuid: users(:admin).uuid)
-      g = Group.create!(group_class: 'role', name: 'repo-writers')
-      u1 = users(:active)
-      u2 = users(:spectator)
-      Link.create!(tail_uuid: g.uuid, head_uuid: r.uuid, link_class: 'permission', name: 'can_manage')
-      Link.create!(tail_uuid: u1.uuid, head_uuid: g.uuid, link_class: 'permission', name: 'can_write')
-      Link.create!(tail_uuid: u2.uuid, head_uuid: g.uuid, link_class: 'permission', name: 'can_read')
-
-      r = Repository.create!(name: 'admin/groupreadonly', owner_uuid: users(:admin).uuid)
-      g = Group.create!(group_class: 'role', name: 'repo-readers')
-      u1 = users(:active)
-      u2 = users(:spectator)
-      Link.create!(tail_uuid: g.uuid, head_uuid: r.uuid, link_class: 'permission', name: 'can_read')
-      Link.create!(tail_uuid: u1.uuid, head_uuid: g.uuid, link_class: 'permission', name: 'can_write')
-      Link.create!(tail_uuid: u2.uuid, head_uuid: g.uuid, link_class: 'permission', name: 'can_read')
-    end
-    authorize_with :admin
-    get :get_all_permissions
-    assert_response :success
-    json_response['repositories'].each do |repo|
-      repo['user_permissions'].each do |user_uuid, perms|
-        u = User.find_by_uuid(user_uuid)
-        if perms['can_read']
-          assert u.can? read: repo['uuid']
-          assert_match(/R/, perms['gitolite_permissions'])
-        else
-          refute_match(/R/, perms['gitolite_permissions'])
-        end
-        if perms['can_write']
-          assert u.can? write: repo['uuid']
-          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'])
-        end
-      end
-    end
-  end
-
-  test "default index includes fetch_url" do
-    authorize_with :active
-    get(:index)
-    assert_response :success
-    assert_includes(json_response["items"].map { |r| r["fetch_url"] },
-                    "git at git.zzzzz.arvadosapi.com:active/foo.git")
-  end
-
-  [
-    {cfg: "GitSSH.ExternalURL", cfgval: URI("ssh://git@example.com"), match: %r"^git at example.com:"},
-    {cfg: "GitSSH.ExternalURL", cfgval: URI(""), match: %r"^git at git.zzzzz.arvadosapi.com:"},
-    {cfg: "GitSSH", cfgval: false, refute: /^git@/ },
-    {cfg: "GitHTTP.ExternalURL", cfgval: URI("https://example.com/"), match: %r"^https://example.com/"},
-    {cfg: "GitHTTP.ExternalURL", cfgval: URI(""), match: %r"^https://git.zzzzz.arvadosapi.com/"},
-    {cfg: "GitHTTP", cfgval: false, refute: /^http/ },
-  ].each do |expect|
-    test "set #{expect[:cfg]} to #{expect[:cfgval]}" do
-      ConfigLoader.set_cfg Rails.configuration.Services, expect[:cfg].to_s, expect[:cfgval]
-      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[:match].match u
-                  end),
-                 "no match for #{expect[:match]} in #{r['clone_urls'].inspect}")
-        end
-      end
-    end
-  end
-
-  test "select push_url in index" do
-    authorize_with :active
-    get(:index, params: {select: ["uuid", "push_url"]})
-    assert_response :success
-    assert_includes(json_response["items"].map { |r| r["push_url"] },
-                    "git at git.zzzzz.arvadosapi.com:active/foo.git")
-  end
-
-  test "select clone_urls in index" do
-    authorize_with :active
-    get(:index, params: {select: ["uuid", "clone_urls"]})
-    assert_response :success
-    assert_includes(json_response["items"].map { |r| r["clone_urls"] }.flatten,
-                    "git at git.zzzzz.arvadosapi.com:active/foo.git")
-  end
-end
diff --git a/services/api/test/functional/arvados/v1/schema_controller_test.rb b/services/api/test/functional/arvados/v1/schema_controller_test.rb
index 65a2b64b8a..39b0cfe8f9 100644
--- a/services/api/test/functional/arvados/v1/schema_controller_test.rb
+++ b/services/api/test/functional/arvados/v1/schema_controller_test.rb
@@ -60,16 +60,16 @@ class Arvados::V1::SchemaControllerTest < ActionController::TestCase
     assert_response :success
     discovery_doc = JSON.parse(@response.body)
     assert_equal('POST',
-                 discovery_doc['resources']['jobs']['methods']['create']['httpMethod'])
+                 discovery_doc['resources']['collections']['methods']['create']['httpMethod'])
   end
 
   test "non-empty disable_api_methods" do
     Rails.configuration.API.DisabledAPIs = ConfigLoader.to_OrderedOptions(
-      {'jobs.create'=>{}, 'pipeline_instances.create'=>{}, 'pipeline_templates.create'=>{}})
+      {'collections.create'=>{}, 'workflows.create'=>{}})
     get :index
     assert_response :success
     discovery_doc = JSON.parse(@response.body)
-    ['jobs', 'pipeline_instances', 'pipeline_templates'].each do |r|
+    ['collections', 'workflows'].each do |r|
       refute_includes(discovery_doc['resources'][r]['methods'].keys(), 'create')
     end
   end
@@ -97,10 +97,10 @@ class Arvados::V1::SchemaControllerTest < ActionController::TestCase
 
     discovery_doc = JSON.parse(@response.body)
 
-    specimens_index_params = discovery_doc['resources']['specimens']['methods']['index']['parameters']  # no changes from super
+    workflows_index_params = discovery_doc['resources']['workflows']['methods']['index']['parameters']  # no changes from super
     coll_index_params = discovery_doc['resources']['collections']['methods']['index']['parameters']
 
-    assert_equal (specimens_index_params.keys + ['include_trash', 'include_old_versions']).sort, coll_index_params.keys.sort
+    assert_equal (workflows_index_params.keys + ['include_trash', 'include_old_versions']).sort, coll_index_params.keys.sort
 
     include_trash_param = coll_index_params['include_trash']
     assert_equal 'boolean', include_trash_param['type']
diff --git a/services/api/test/functional/arvados/v1/specimens_controller_test.rb b/services/api/test/functional/arvados/v1/specimens_controller_test.rb
deleted file mode 100644
index df681e6f5e..0000000000
--- a/services/api/test/functional/arvados/v1/specimens_controller_test.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class Arvados::V1::SpecimensControllerTest < ActionController::TestCase
-end
diff --git a/services/api/test/functional/arvados/v1/traits_controller_test.rb b/services/api/test/functional/arvados/v1/traits_controller_test.rb
deleted file mode 100644
index 3c8d097350..0000000000
--- a/services/api/test/functional/arvados/v1/traits_controller_test.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class Arvados::V1::TraitsControllerTest < ActionController::TestCase
-end
diff --git a/services/api/test/functional/arvados/v1/users_controller_test.rb b/services/api/test/functional/arvados/v1/users_controller_test.rb
index cc0b5e1320..2b643d4b27 100644
--- a/services/api/test/functional/arvados/v1/users_controller_test.rb
+++ b/services/api/test/functional/arvados/v1/users_controller_test.rb
@@ -121,12 +121,10 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     assert_nil updated['username'], 'expected no username'
   end
 
-  test "create user with user, vm and repo as input" do
+  test "create user with user and vm as input" do
     authorize_with :admin
-    repo_name = 'usertestrepo'
 
     post :setup, params: {
-      repo_name: repo_name,
       user: {
         uuid: 'zzzzz-tpzed-abcdefghijklmno',
         first_name: "in_create_test_first_name",
@@ -145,11 +143,8 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     assert_not_nil created['email'], 'expected non-nil email'
     assert_nil created['identity_url'], 'expected no identity_url'
 
-    # repo link and link add user to 'All users' group
-    verify_links_added 3
-
-    verify_link response_items, 'arvados#repository', true, 'permission', 'can_manage',
-        "foo/#{repo_name}", created['uuid'], 'arvados#repository', true, 'Repository'
+    # added links: vm permission, 'all users' group
+    verify_links_added 2
 
     verify_link response_items, 'arvados#group', true, 'permission', 'can_write',
         'All users', created['uuid'], 'arvados#group', true, 'Group'
@@ -165,7 +160,6 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
 
     post :setup, params: {
       uuid: 'bogus_uuid',
-      repo_name: 'usertestrepo',
       vm_uuid: @vm_uuid
     }
     response_body = JSON.parse(@response.body)
@@ -179,7 +173,6 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
 
     post :setup, params: {
       user: {uuid: 'bogus_uuid'},
-      repo_name: 'usertestrepo',
       vm_uuid: @vm_uuid,
     }
     response_body = JSON.parse(@response.body)
@@ -193,7 +186,6 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     authorize_with :admin
 
     post :setup, params: {
-      repo_name: 'usertestrepo',
       vm_uuid: @vm_uuid,
     }
     response_body = JSON.parse(@response.body)
@@ -208,7 +200,6 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
 
     post :setup, params: {
       user: {},
-      repo_name: 'usertestrepo',
       vm_uuid: @vm_uuid,
     }
     response_body = JSON.parse(@response.body)
@@ -218,13 +209,12 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
         'Expected ArgumentError'
   end
 
-  test "invoke setup with existing uuid, vm and repo and verify links" do
+  test "invoke setup with existing uuid and vm permission, and verify links" do
     authorize_with :admin
     inactive_user = users(:inactive)
 
     post :setup, params: {
       uuid: users(:inactive).uuid,
-      repo_name: 'usertestrepo',
       vm_uuid: @vm_uuid
     }
 
@@ -238,10 +228,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     assert_equal inactive_user['email'], resp_obj['email'],
         'expecting inactive user email'
 
-    # expect repo and vm links
-    verify_link response_items, 'arvados#repository', true, 'permission', 'can_manage',
-        'inactiveuser/usertestrepo', resp_obj['uuid'], 'arvados#repository', true, 'Repository'
-
+    # expect vm permission link
     verify_link response_items, 'arvados#virtualMachine', true, 'permission', 'can_login',
         @vm_uuid, resp_obj['uuid'], 'arvados#virtualMachine', false, 'VirtualMachine'
   end
@@ -266,7 +253,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
         'expecting inactive user email'
   end
 
-  test "setup user with valid email and repo as input" do
+  test "setup user with valid email and repo(ignored) as input" do
     authorize_with :admin
 
     post :setup, params: {
@@ -280,15 +267,14 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     assert_not_nil response_object['uuid'], 'expected uuid for the new user'
     assert_equal response_object['email'], 'foo at example.com', 'expected given email'
 
-    # three extra links; system_group, group and repo perms
-    verify_links_added 3
+    # added links: system_group, 'all users' group.
+    verify_links_added 2
   end
 
   test "setup user with fake vm and expect error" do
     authorize_with :admin
 
     post :setup, params: {
-      repo_name: 'usertestrepo',
       vm_uuid: 'no_such_vm',
       user: {email: 'foo at example.com'},
     }
@@ -300,11 +286,10 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
           'Expected RuntimeError: No vm found for no_such_vm'
   end
 
-  test "setup user with valid email, repo and real vm as input" do
+  test "setup user with valid email and real vm as input" do
     authorize_with :admin
 
     post :setup, params: {
-      repo_name: 'usertestrepo',
       vm_uuid: @vm_uuid,
       user: {email: 'foo at example.com'}
     }
@@ -315,8 +300,8 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     assert_not_nil response_object['uuid'], 'expected uuid for the new user'
     assert_equal response_object['email'], 'foo at example.com', 'expected given email'
 
-    # four extra links; system_group, group, vm, repo
-    verify_links_added 4
+    # added links; system_group, 'all users' group, vm.
+    verify_links_added 3
   end
 
   test "setup user with valid email, no vm and no repo as input" do
@@ -332,24 +317,20 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     assert_not_nil response_object['uuid'], 'expected uuid for new user'
     assert_equal response_object['email'], 'foo at example.com', 'expected given email'
 
-    # two extra links; system_group, and group
+    # added links; system_group, 'all users' group.
     verify_links_added 2
 
     verify_link response_items, 'arvados#group', true, 'permission', 'can_write',
         'All users', response_object['uuid'], 'arvados#group', true, 'Group'
 
-    verify_link response_items, 'arvados#repository', false, 'permission', 'can_manage',
-        'foo/usertestrepo', response_object['uuid'], 'arvados#repository', true, 'Repository'
-
     verify_link response_items, 'arvados#virtualMachine', false, 'permission', 'can_login',
         nil, response_object['uuid'], 'arvados#virtualMachine', false, 'VirtualMachine'
   end
 
-  test "setup user with email, first name, repo name and vm uuid" do
+  test "setup user with email, first name, and vm uuid" do
     authorize_with :admin
 
     post :setup, params: {
-      repo_name: 'usertestrepo',
       vm_uuid: @vm_uuid,
       user: {
         first_name: 'test_first_name',
@@ -365,8 +346,8 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     assert_equal 'test_first_name', response_object['first_name'],
         'expecting first name'
 
-    # four extra links; system_group, group, repo and vm
-    verify_links_added 4
+    # added links: system_group, 'all users' group, vm.
+    verify_links_added 3
   end
 
   test "setup user with an existing user email and check different object is created" do
@@ -374,7 +355,6 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     inactive_user = users(:inactive)
 
     post :setup, params: {
-      repo_name: 'usertestrepo',
       user: {
         email: inactive_user['email']
       }
@@ -387,15 +367,14 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     assert_not_equal response_object['uuid'], inactive_user['uuid'],
         'expected different uuid after create operation'
     assert_equal inactive_user['email'], response_object['email'], 'expected given email'
-    # system_group, group, and repo. No vm link.
-    verify_links_added 3
+    # added links: system_group, 'all users' group.
+    verify_links_added 2
   end
 
   test "setup user with openid prefix" do
     authorize_with :admin
 
     post :setup, params: {
-      repo_name: 'usertestrepo',
       user: {
         first_name: "in_create_test_first_name",
         last_name: "test_last_name",
@@ -413,12 +392,8 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     assert_not_nil created['email'], 'expected non-nil email'
     assert_nil created['identity_url'], 'expected no identity_url'
 
-    # verify links
-    # three new links: system_group, repo, and 'All users' group.
-    verify_links_added 3
-
-    verify_link response_items, 'arvados#repository', true, 'permission', 'can_manage',
-        'foo/usertestrepo', created['uuid'], 'arvados#repository', true, 'Repository'
+    # added links: system_group, 'all users' group.
+    verify_links_added 2
 
     verify_link response_items, 'arvados#group', true, 'permission', 'can_write',
         'All users', created['uuid'], 'arvados#group', true, 'Group'
@@ -427,7 +402,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
         nil, created['uuid'], 'arvados#virtualMachine', false, 'VirtualMachine'
   end
 
-  test "setup user with user, vm and repo and verify links" do
+  test "setup user with user and vm, and verify links" do
     authorize_with :admin
 
     post :setup, params: {
@@ -437,7 +412,6 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
         email: "foo at example.com"
       },
       vm_uuid: @vm_uuid,
-      repo_name: 'usertestrepo',
     }
 
     assert_response :success
@@ -450,14 +424,11 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     assert_not_nil created['email'], 'expected non-nil email'
     assert_nil created['identity_url'], 'expected no identity_url'
 
-    # four new links: system_group, repo, vm and 'All users' group link
-    verify_links_added 4
+    # added links: system_group, 'all users' group, vm
+    verify_links_added 3
 
     # system_group isn't part of the response.  See User#add_system_group_permission_link
 
-    verify_link response_items, 'arvados#repository', true, 'permission', 'can_manage',
-        'foo/usertestrepo', created['uuid'], 'arvados#repository', true, 'Repository'
-
     verify_link response_items, 'arvados#group', true, 'permission', 'can_write',
         'All users', created['uuid'], 'arvados#group', true, 'Group'
 
@@ -493,13 +464,11 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
           'Expected Forbidden error'
   end
 
-  test "setup active user with repo and no vm" do
+  test "setup active user with no vm" do
     authorize_with :admin
     active_user = users(:active)
 
-    # invoke setup with a repository
     post :setup, params: {
-      repo_name: 'usertestrepo',
       uuid: active_user['uuid']
     }
 
@@ -510,13 +479,10 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
 
     assert_equal active_user[:email], created['email'], 'expected input email'
 
-     # verify links
+    # verify links
     verify_link response_items, 'arvados#group', true, 'permission', 'can_write',
         'All users', created['uuid'], 'arvados#group', true, 'Group'
 
-    verify_link response_items, 'arvados#repository', true, 'permission', 'can_manage',
-        'active/usertestrepo', created['uuid'], 'arvados#repository', true, 'Repository'
-
     verify_link response_items, 'arvados#virtualMachine', false, 'permission', 'can_login',
         nil, created['uuid'], 'arvados#virtualMachine', false, 'VirtualMachine'
   end
@@ -524,13 +490,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
   test "setup active user with vm and no repo" do
     authorize_with :admin
     active_user = users(:active)
-    repos_query = Repository.where(owner_uuid: active_user.uuid)
-    repo_link_query = Link.where(tail_uuid: active_user.uuid,
-                                 link_class: "permission", name: "can_manage")
-    repos_count = repos_query.count
-    repo_link_count = repo_link_query.count
 
-    # invoke setup with a repository
     post :setup, params: {
       vm_uuid: @vm_uuid,
       uuid: active_user['uuid'],
@@ -548,9 +508,6 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     verify_link response_items, 'arvados#group', true, 'permission', 'can_write',
         'All users', created['uuid'], 'arvados#group', true, 'Group'
 
-    assert_equal(repos_count, repos_query.count)
-    assert_equal(repo_link_count, repo_link_query.count)
-
     verify_link response_items, 'arvados#virtualMachine', true, 'permission', 'can_login',
         @vm_uuid, created['uuid'], 'arvados#virtualMachine', false, 'VirtualMachine'
   end
@@ -655,7 +612,6 @@ The Arvados team.
     authorize_with :admin
     active_user = users(:active)
 
-    # invoke setup with a repository
     put :update, params: {
           id: active_user['uuid'],
           user: {
diff --git a/services/api/test/functional/database_controller_test.rb b/services/api/test/functional/database_controller_test.rb
index ef1d0c6d05..ea44cbf453 100644
--- a/services/api/test/functional/database_controller_test.rb
+++ b/services/api/test/functional/database_controller_test.rb
@@ -40,12 +40,12 @@ class DatabaseControllerTest < ActionController::TestCase
   test "reset succeeds with admin token" do
     new_uuid = nil
     act_as_system_user do
-      new_uuid = Specimen.create.uuid
+      new_uuid = Collection.create.uuid
     end
-    assert_not_empty Specimen.where(uuid: new_uuid)
+    assert_not_empty Collection.where(uuid: new_uuid)
     authorize_with :admin
     post :reset
     assert_response 200
-    assert_empty Specimen.where(uuid: new_uuid)
+    assert_empty Collection.where(uuid: new_uuid)
   end
 end
diff --git a/services/api/test/functional/sys_controller_test.rb b/services/api/test/functional/sys_controller_test.rb
index e13d702983..ab304c1c7c 100644
--- a/services/api/test/functional/sys_controller_test.rb
+++ b/services/api/test/functional/sys_controller_test.rb
@@ -96,7 +96,6 @@ class SysControllerTest < ActionController::TestCase
     g_bar = groups(:trashed_subproject)
     g_baz = groups(:trashed_subproject3)
     col = collections(:collection_in_trashed_subproject)
-    job = jobs(:job_in_trashed_project)
     cr = container_requests(:cr_in_trashed_project)
     # Save how many objects were before the sweep
     user_nr_was = User.all.length
@@ -104,12 +103,10 @@ class SysControllerTest < ActionController::TestCase
     group_nr_was = Group.where('group_class<>?', 'project').length
     project_nr_was = Group.where(group_class: 'project').length
     cr_nr_was = ContainerRequest.all.length
-    job_nr_was = Job.all.length
     assert_not_empty Group.where(uuid: g_foo.uuid)
     assert_not_empty Group.where(uuid: g_bar.uuid)
     assert_not_empty Group.where(uuid: g_baz.uuid)
     assert_not_empty Collection.where(uuid: col.uuid)
-    assert_not_empty Job.where(uuid: job.uuid)
     assert_not_empty ContainerRequest.where(uuid: cr.uuid)
 
     authorize_with :admin
@@ -120,7 +117,6 @@ class SysControllerTest < ActionController::TestCase
     assert_empty Group.where(uuid: g_bar.uuid)
     assert_empty Group.where(uuid: g_baz.uuid)
     assert_empty Collection.where(uuid: col.uuid)
-    assert_empty Job.where(uuid: job.uuid)
     assert_empty ContainerRequest.where(uuid: cr.uuid)
     # No unwanted deletions should have happened
     assert_equal user_nr_was, User.all.length
@@ -129,7 +125,6 @@ class SysControllerTest < ActionController::TestCase
     assert_equal group_nr_was, Group.where('group_class<>?', 'project').length
     assert_equal project_nr_was-3, Group.where(group_class: 'project').length
     assert_equal cr_nr_was-1, ContainerRequest.all.length
-    assert_equal job_nr_was-1, Job.all.length
   end
 
 end
diff --git a/services/api/test/helpers/git_test_helper.rb b/services/api/test/helpers/git_test_helper.rb
deleted file mode 100644
index cb30f68015..0000000000
--- a/services/api/test/helpers/git_test_helper.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'fileutils'
-require 'tmpdir'
-
-# Commit log for "foo" repository in test.git.tar
-# main is the main branch
-# b1 is a branch off of main 
-# tag1 is a tag
-#
-# 1de84a8 * b1
-# 077ba2a * main
-# 4fe459a * tag1
-# 31ce37f * foo
-
-module GitTestHelper
-  def self.included base
-    base.setup do
-      # Extract the test repository data into the default test
-      # environment's Rails.configuration.Git.Repositories. (We
-      # don't use that config setting here, though: it doesn't seem
-      # worth the risk of stepping on a real git repo root.)
-      @tmpdir = Rails.root.join 'tmp', 'git'
-      FileUtils.mkdir_p @tmpdir
-      system("tar", "-xC", @tmpdir.to_s, "-f", "test/test.git.tar")
-      Rails.configuration.Git.Repositories = "#{@tmpdir}/test"
-      Rails.configuration.Containers.JobsAPI.GitInternalDir = "#{@tmpdir}/internal.git"
-    end
-
-    base.teardown do
-      FileUtils.remove_entry CommitsHelper.cache_dir_base, true
-      FileUtils.mkdir_p @tmpdir
-      system("tar", "-xC", @tmpdir.to_s, "-f", "test/test.git.tar")
-    end
-  end
-
-  def internal_tag tag
-    IO.read "|git --git-dir #{Rails.configuration.Containers.JobsAPI.GitInternalDir.shellescape} log --format=format:%H -n1 #{tag.shellescape}"
-  end
-
-  # Intercept fetch_remote_repository and fetch from a specified url
-  # or local fixture instead of the remote url requested. fakeurl can
-  # be a url (probably starting with file:///) or the name of a
-  # fixture (as a symbol)
-  def fetch_remote_from_local_repo url, fakeurl
-    if fakeurl.is_a? Symbol
-      fakeurl = 'file://' + repositories(fakeurl).server_path
-    end
-    CommitsHelper.expects(:fetch_remote_repository).once.with do |gitdir, giturl|
-      if giturl == url
-        CommitsHelper.unstub(:fetch_remote_repository)
-        CommitsHelper.fetch_remote_repository gitdir, fakeurl
-        true
-      end
-    end
-  end
-end
diff --git a/services/api/test/helpers/users_test_helper.rb b/services/api/test/helpers/users_test_helper.rb
index e106d994cd..c4dc72d3ba 100644
--- a/services/api/test/helpers/users_test_helper.rb
+++ b/services/api/test/helpers/users_test_helper.rb
@@ -54,14 +54,10 @@ module UsersTestHelper
     # these don't get added any more!  they shouldn't appear ever.
     assert !oid_login_perms.any?, "expected all oid_login_perms deleted"
 
+    # these don't get added any more!  they shouldn't appear ever.
     repo_perms = Link.where(tail_uuid: uuid,
-                            link_class: 'permission',
-                            name: 'can_manage').where("head_uuid like ?", Repository.uuid_like_pattern)
-    if expect_repo_perms
-      assert repo_perms.any?, "expected repo_perms"
-    else
-      assert !repo_perms.any?, "expected all repo_perms deleted"
-    end
+                            link_class: 'permission').where("head_uuid like ?", '_____-s0uqq-_______________')
+    assert !repo_perms.any?, "expected all repo_perms deleted"
 
     vm_login_perms = Link.
       where(tail_uuid: uuid,
diff --git a/services/api/test/integration/api_client_authorizations_scopes_test.rb b/services/api/test/integration/api_client_authorizations_scopes_test.rb
index 3b28a3163f..83a6337644 100644
--- a/services/api/test/integration/api_client_authorizations_scopes_test.rb
+++ b/services/api/test/integration/api_client_authorizations_scopes_test.rb
@@ -44,15 +44,15 @@ class ApiTokensScopeTest < ActionDispatch::IntegrationTest
     assert_response 403
    end
 
-  test "specimens token can see exactly owned specimens" do
-    get_args = {params: {}, headers: auth(:active_specimens)}
-    get(v1_url('specimens'), **get_args)
+  test "collections token can see exactly owned collections" do
+    get_args = {params: {}, headers: auth(:active_all_collections)}
+    get(v1_url('collections'), **get_args)
     assert_response 403
-    get(v1_url('specimens', specimens(:owned_by_active_user).uuid), **get_args)
+    get(v1_url('collections', collections(:collection_owned_by_active).uuid), **get_args)
     assert_response :success
-    head(v1_url('specimens', specimens(:owned_by_active_user).uuid), **get_args)
+    head(v1_url('collections', collections(:collection_owned_by_active).uuid), **get_args)
     assert_response :success
-    get(v1_url('specimens', specimens(:owned_by_spectator).uuid), **get_args)
+    get(v1_url('collections', collections(:collection_owned_by_foo).uuid), **get_args)
     assert_includes(403..404, @response.status)
   end
 
diff --git a/services/api/test/integration/database_reset_test.rb b/services/api/test/integration/database_reset_test.rb
index 7015453a9a..aa778dbf9f 100644
--- a/services/api/test/integration/database_reset_test.rb
+++ b/services/api/test/integration/database_reset_test.rb
@@ -31,22 +31,22 @@ class DatabaseResetTest < ActionDispatch::IntegrationTest
     post '/database/reset', params: {}, headers: admin_auth
     assert_response :success
 
-    post '/arvados/v1/specimens', params: {specimen: '{}'}, headers: active_auth
+    post '/arvados/v1/collections', params: {collection: '{}'}, headers: active_auth
     assert_response :success
     new_uuid = json_response['uuid']
 
-    get '/arvados/v1/specimens/'+new_uuid, params: {}, headers: active_auth
+    get '/arvados/v1/collections/'+new_uuid, params: {}, headers: active_auth
     assert_response :success
 
-    put('/arvados/v1/specimens/'+new_uuid,
-      params: {specimen: '{"properties":{}}'},
+    put('/arvados/v1/collections/'+new_uuid,
+      params: {collection: '{"properties":{}}'},
       headers: active_auth)
     assert_response :success
 
-    delete '/arvados/v1/specimens/'+new_uuid, params: {}, headers: active_auth
+    delete '/arvados/v1/collections/'+new_uuid, params: {}, headers: active_auth
     assert_response :success
 
-    get '/arvados/v1/specimens/'+new_uuid, params: {}, headers: active_auth
+    get '/arvados/v1/collections/'+new_uuid, params: {}, headers: active_auth
     assert_response 404
   end
 
@@ -54,14 +54,14 @@ class DatabaseResetTest < ActionDispatch::IntegrationTest
     active_auth = auth(:active)
     admin_auth = auth(:admin)
 
-    old_uuid = specimens(:owned_by_active_user).uuid
+    old_uuid = collections(:collection_owned_by_active).uuid
     authorize_with :admin
     post '/database/reset', params: {}, headers: admin_auth
     assert_response :success
 
-    delete '/arvados/v1/specimens/' + old_uuid, params: {}, headers: active_auth
+    delete '/arvados/v1/collections/' + old_uuid, params: {}, headers: active_auth
     assert_response :success
-    post '/arvados/v1/specimens', params: {specimen: '{}'}, headers: active_auth
+    post '/arvados/v1/collections', params: {collection: '{}'}, headers: active_auth
     assert_response :success
     new_uuid = json_response['uuid']
 
@@ -69,10 +69,10 @@ class DatabaseResetTest < ActionDispatch::IntegrationTest
     post '/database/reset', params: {}, headers: admin_auth
     assert_response :success
 
-    # New specimen should disappear. Old specimen should reappear.
-    get '/arvados/v1/specimens/'+new_uuid, params: {}, headers: active_auth
+    # New collection should disappear. Old collection should reappear.
+    get '/arvados/v1/collections/'+new_uuid, params: {}, headers: active_auth
     assert_response 404
-    get '/arvados/v1/specimens/'+old_uuid, params: {}, headers: active_auth
+    get '/arvados/v1/collections/'+old_uuid, params: {}, headers: active_auth
     assert_response :success
   end
 end
diff --git a/services/api/test/integration/groups_test.rb b/services/api/test/integration/groups_test.rb
index e76f2b5406..bc5a08c2c8 100644
--- a/services/api/test/integration/groups_test.rb
+++ b/services/api/test/integration/groups_test.rb
@@ -140,7 +140,7 @@ class GroupsTest < ActionDispatch::IntegrationTest
 
   test 'count none works with offset' do
     first_results = nil
-    (0..10).each do |offset|
+    (0..5).each do |offset|
       get "/arvados/v1/groups/contents", params: {
         id: groups(:aproject).uuid,
         offset: offset,
diff --git a/services/api/test/integration/jobs_api_test.rb b/services/api/test/integration/jobs_api_test.rb
deleted file mode 100644
index 76d4fff59e..0000000000
--- a/services/api/test/integration/jobs_api_test.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class JobsApiTest < ActionDispatch::IntegrationTest
-end
diff --git a/services/api/test/integration/login_workflow_test.rb b/services/api/test/integration/login_workflow_test.rb
index ba3b2ac6e3..7ad95ceebf 100644
--- a/services/api/test/integration/login_workflow_test.rb
+++ b/services/api/test/integration/login_workflow_test.rb
@@ -6,8 +6,8 @@ require 'test_helper'
 
 class LoginWorkflowTest < ActionDispatch::IntegrationTest
   test "default prompt to login is JSON" do
-    post('/arvados/v1/specimens',
-      params: {specimen: {}},
+    post('/arvados/v1/collections',
+      params: {collection: {}},
       headers: {'HTTP_ACCEPT' => ''})
     assert_response 401
     json_response['errors'].each do |err|
@@ -16,8 +16,8 @@ class LoginWorkflowTest < ActionDispatch::IntegrationTest
   end
 
   test "login prompt respects JSON Accept header" do
-    post('/arvados/v1/specimens',
-      params: {specimen: {}},
+    post('/arvados/v1/collections',
+      params: {collection: {}},
       headers: {'HTTP_ACCEPT' => 'application/json'})
     assert_response 401
     json_response['errors'].each do |err|
@@ -26,8 +26,8 @@ class LoginWorkflowTest < ActionDispatch::IntegrationTest
   end
 
   test "login prompt respects HTML Accept header" do
-    post('/arvados/v1/specimens',
-      params: {specimen: {}},
+    post('/arvados/v1/collections',
+      params: {collection: {}},
       headers: {'HTTP_ACCEPT' => 'text/html'})
     assert_response 302
     assert_match(%r{http://www.example.com/login$}, @response.headers['Location'],
diff --git a/services/api/test/integration/permissions_test.rb b/services/api/test/integration/permissions_test.rb
index d2dce44f01..9636a82011 100644
--- a/services/api/test/integration/permissions_test.rb
+++ b/services/api/test/integration/permissions_test.rb
@@ -302,26 +302,29 @@ class PermissionsTest < ActionDispatch::IntegrationTest
     assert_response 404
   end
 
-  test "RO group-admin finds user's specimens, RW group-admin can update" do
+  test "RO group-admin finds user's collections, RW group-admin can update" do
+    other_user_collection = act_as_user(users(:user_foo_in_sharing_group)) do
+      Collection.create()
+    end
     [[:rominiadmin, false],
      [:miniadmin, true]].each do |which_user, update_should_succeed|
-      get "/arvados/v1/specimens",
+      get "/arvados/v1/collections",
         params: {:format => :json},
         headers: auth(which_user)
       assert_response :success
       resp_uuids = json_response['items'].collect { |i| i['uuid'] }
-      [[true, specimens(:owned_by_active_user).uuid],
-       [true, specimens(:owned_by_private_group).uuid],
-       [false, specimens(:owned_by_spectator).uuid],
+      [[true, collections(:collection_owned_by_active).uuid],
+       [true, collections(:foo_collection_in_aproject).uuid],
+       [false, other_user_collection.uuid],
       ].each do |should_find, uuid|
         assert_equal(should_find, !resp_uuids.index(uuid).nil?,
-                     "%s should%s see %s in specimen list" %
+                     "%s should%s see %s in collection list" %
                      [which_user.to_s,
-                      should_find ? '' : 'not ',
+                      should_find ? '' : ' not',
                       uuid])
-        put "/arvados/v1/specimens/#{uuid}",
+        put "/arvados/v1/collections/#{uuid}",
           params: {
-            :specimen => {
+            :collection => {
               properties: {
                 miniadmin_was_here: true
               }
diff --git a/services/api/test/integration/reader_tokens_test.rb b/services/api/test/integration/reader_tokens_test.rb
index e8e8c910c7..891bffbb1d 100644
--- a/services/api/test/integration/reader_tokens_test.rb
+++ b/services/api/test/integration/reader_tokens_test.rb
@@ -7,20 +7,20 @@ require 'test_helper'
 class ReaderTokensTest < ActionDispatch::IntegrationTest
   fixtures :all
 
-  def spectator_specimen
-    specimens(:owned_by_spectator).uuid
+  def owned_by_foo
+    collections(:collection_owned_by_foo).uuid
   end
 
-  def get_specimens(main_auth, read_auth, formatter=:to_a)
+  def get_collections(main_auth, read_auth, formatter=:to_a)
     params = {}
     params[:reader_tokens] = [api_token(read_auth)].send(formatter) if read_auth
     headers = {}
     headers.merge!(auth(main_auth)) if main_auth
-    get('/arvados/v1/specimens', params: params, headers: headers)
+    get('/arvados/v1/collections', params: params, headers: headers)
   end
 
-  def get_specimen_uuids(main_auth, read_auth, formatter=:to_a)
-    get_specimens(main_auth, read_auth, formatter)
+  def get_collection_uuids(main_auth, read_auth, formatter=:to_a)
+    get_collections(main_auth, read_auth, formatter)
     assert_response :success
     json_response['items'].map { |spec| spec['uuid'] }
   end
@@ -33,26 +33,26 @@ class ReaderTokensTest < ActionDispatch::IntegrationTest
       headers = {}
       expected = 401
     end
-    post('/arvados/v1/specimens.json',
-      params: {specimen: {}, reader_tokens: [api_token(read_auth)].send(formatter)},
+    post('/arvados/v1/collections.json',
+      params: {collection: {}, reader_tokens: [api_token(read_auth)].send(formatter)},
       headers: headers)
     assert_response expected
   end
 
-  test "active user can't see spectator specimen" do
+  test "active user can't see foo-owned collection" do
     # Other tests in this suite assume that the active user doesn't
-    # have read permission to the owned_by_spectator specimen.
+    # have read permission to the owned_by_foo collection.
     # This test checks that this assumption still holds.
-    refute_includes(get_specimen_uuids(:active, nil), spectator_specimen,
-                    ["active user can read the owned_by_spectator specimen",
+    refute_includes(get_collection_uuids(:active, nil), owned_by_foo,
+                    ["active user can read the owned_by_foo collection",
                      "other tests will return false positives"].join(" - "))
   end
 
   [nil, :active_noscope].each do |main_auth|
-    [:spectator, :spectator_specimens].each do |read_auth|
+    [:foo, :foo_collections].each do |read_auth|
       [:to_a, :to_json].each do |formatter|
         test "#{main_auth.inspect} auth with #{formatter} reader token #{read_auth} can#{"'t" if main_auth} read" do
-          get_specimens(main_auth, read_auth)
+          get_collections(main_auth, read_auth)
           assert_response(if main_auth then 403 else 200 end)
         end
 
@@ -65,18 +65,18 @@ class ReaderTokensTest < ActionDispatch::IntegrationTest
 
   test "scopes are still limited with reader tokens" do
     get('/arvados/v1/collections',
-      params: {reader_tokens: [api_token(:spectator_specimens)]},
+      params: {reader_tokens: [api_token(:foo_collections)]},
       headers: auth(:active_noscope))
     assert_response 403
   end
 
   test "reader tokens grant no permissions when expired" do
-    get_specimens(:active_noscope, :expired)
+    get_collections(:active_noscope, :expired)
     assert_response 403
   end
 
   test "reader tokens grant no permissions outside their scope" do
-    refute_includes(get_specimen_uuids(:active, :admin_vm), spectator_specimen,
+    refute_includes(get_collection_uuids(:active, :admin_vm), owned_by_foo,
                     "scoped reader token granted permissions out of scope")
   end
 end
diff --git a/services/api/test/integration/serialized_encoding_test.rb b/services/api/test/integration/serialized_encoding_test.rb
index f41c033b39..bf2d0062f2 100644
--- a/services/api/test/integration/serialized_encoding_test.rb
+++ b/services/api/test/integration/serialized_encoding_test.rb
@@ -3,26 +3,13 @@
 # SPDX-License-Identifier: AGPL-3.0
 
 require 'test_helper'
-require 'helpers/git_test_helper'
 
 class SerializedEncodingTest < ActionDispatch::IntegrationTest
-  include GitTestHelper
-
   fixtures :all
 
   {
     api_client_authorization: {scopes: []},
-
-    human: {properties: {eye_color: 'gray'}},
-
     link: {link_class: 'test', name: 'test', properties: {foo: :bar}},
-
-    node: {info: {uptime: 1234}},
-
-    specimen: {properties: {eye_color: 'meringue'}},
-
-    trait: {properties: {eye_color: 'brown'}},
-
     user: {prefs: {cookies: 'thin mint'}},
   }.each_pair do |resource, postdata|
     test "create json-encoded #{resource.to_s}" do
diff --git a/services/api/test/integration/user_sessions_test.rb b/services/api/test/integration/user_sessions_test.rb
index eb49cf832e..2b37454218 100644
--- a/services/api/test/integration/user_sessions_test.rb
+++ b/services/api/test/integration/user_sessions_test.rb
@@ -87,22 +87,20 @@ class UserSessionsApiTest < ActionDispatch::IntegrationTest
 
   # Test various combinations of auto_setup configuration and email
   # address provided during a new user's first session setup.
-  [{result: :nope, email: nil, cfg: {auto: true, repo: true, vm: true}},
+  [{result: :nope, email: nil, cfg: {auto: true, vm: true}},
    {result: :yup, email: nil, cfg: {auto: true}},
-   {result: :nope, email: '@example.com', cfg: {auto: true, repo: true, vm: true}},
+   {result: :nope, email: '@example.com', cfg: {auto: true, vm: true}},
    {result: :yup, email: '@example.com', cfg: {auto: true}},
-   {result: :nope, email: 'root@', cfg: {auto: true, repo: true, vm: true}},
-   {result: :nope, email: 'root@', cfg: {auto: true, repo: true}},
    {result: :nope, email: 'root@', cfg: {auto: true, vm: true}},
-   {result: :yup, email: 'root@', cfg: {auto: true}},
-   {result: :nope, email: 'gitolite@', cfg: {auto: true, repo: true}},
+   {result: :nope, email: 'root@', cfg: {auto: true}},
+   {result: :nope, email: 'gitolite@', cfg: {auto: true}},
    {result: :nope, email: '*_*@', cfg: {auto: true, vm: true}},
    {result: :yup, email: 'toor@', cfg: {auto: true, vm: true, repo: true}},
    {result: :yup, email: 'foo@', cfg: {auto: true, vm: true},
      uniqprefix: 'foo'},
-   {result: :yup, email: 'foo@', cfg: {auto: true, repo: true},
+   {result: :yup, email: 'foo@', cfg: {auto: true},
      uniqprefix: 'foo'},
-   {result: :yup, email: 'auto_setup_vm_login@', cfg: {auto: true, repo: true},
+   {result: :yup, email: 'auto_setup_vm_login@', cfg: {auto: true},
      uniqprefix: 'auto_setup_vm_login'},
    ].each do |testcase|
     test "user auto-activate #{testcase.inspect}" do
@@ -111,23 +109,16 @@ class UserSessionsApiTest < ActionDispatch::IntegrationTest
       Rails.configuration.Users.AutoSetupNewUsers = testcase[:cfg][:auto]
       Rails.configuration.Users.AutoSetupNewUsersWithVmUUID =
         (testcase[:cfg][:vm] ? virtual_machines(:testvm).uuid : "")
-      Rails.configuration.Users.AutoSetupNewUsersWithRepository =
-        testcase[:cfg][:repo]
 
       mock_auth_with(email: testcase[:email])
       u = assigns(:user)
       vm_links = Link.where('link_class=? and tail_uuid=? and head_uuid like ?',
                             'permission', u.uuid,
                             '%-' + VirtualMachine.uuid_prefix + '-%')
-      repo_links = Link.where('link_class=? and tail_uuid=? and head_uuid like ?',
-                              'permission', u.uuid,
-                              '%-' + Repository.uuid_prefix + '-%')
-      repos = Repository.where('uuid in (?)', repo_links.collect(&:head_uuid))
       case u[:result]
       when :nope
         assert_equal false, u.is_invited, "should not have been set up"
         assert_empty vm_links, "should not have VM login permission"
-        assert_empty repo_links, "should not have repo permission"
       when :yup
         assert_equal true, u.is_invited
         if testcase[:cfg][:vm]
@@ -135,21 +126,11 @@ class UserSessionsApiTest < ActionDispatch::IntegrationTest
         else
           assert_empty vm_links, "should not have VM login permission"
         end
-        if testcase[:cfg][:repo]
-          assert_equal 1, repo_links.count, "wrong number of repo perm links"
-          assert_equal 1, repos.count, "wrong number of repos"
-          assert_equal 'can_manage', repo_links.first.name, "wrong perm type"
-        else
-          assert_empty repo_links, "should not have repo permission"
-        end
       end
       if (prefix = testcase[:uniqprefix])
         # This email address conflicts with a test fixture. Make sure
-        # every VM login and repository name got digits added to make
-        # it unique.
-        (repos.collect(&:name) +
-         vm_links.collect { |link| link.properties['username'] }
-         ).each do |name|
+        # every VM login got digits added to make it unique.
+        vm_links.collect { |link| link.properties['username'] }.each do |name|
           r = name.match(/^(.{#{prefix.length}})(\d+)$/)
           assert_not_nil r, "#{name.inspect} does not match {prefix}\\d+"
           assert_equal(prefix, r[1],
diff --git a/services/api/test/integration/users_test.rb b/services/api/test/integration/users_test.rb
index f8956b21e2..a7d6245443 100644
--- a/services/api/test/integration/users_test.rb
+++ b/services/api/test/integration/users_test.rb
@@ -9,11 +9,8 @@ class UsersTest < ActionDispatch::IntegrationTest
   include UsersTestHelper
 
   test "setup user multiple times" do
-    repo_name = 'usertestrepo'
-
     post "/arvados/v1/users/setup",
       params: {
-        repo_name: repo_name,
         user: {
           uuid: 'zzzzz-tpzed-abcdefghijklmno',
           first_name: "in_create_test_first_name",
@@ -35,10 +32,7 @@ class UsersTest < ActionDispatch::IntegrationTest
     assert_not_nil created['email'], 'expected non-nil email'
     assert_nil created['identity_url'], 'expected no identity_url'
 
-    # repo link and link add user to 'All users' group
-
-    verify_link response_items, 'arvados#repository', true, 'permission', 'can_manage',
-        'foo/usertestrepo', created['uuid'], 'arvados#repository', true, 'Repository'
+    # link to add user to 'All users' group
 
     verify_link response_items, 'arvados#group', true, 'permission', 'can_write',
         'All users', created['uuid'], 'arvados#group', true, 'Group'
@@ -51,7 +45,6 @@ class UsersTest < ActionDispatch::IntegrationTest
     # invoke setup again with the same data
     post "/arvados/v1/users/setup",
       params: {
-        repo_name: repo_name,
         vm_uuid: virtual_machines(:testvm).uuid,
         user: {
           uuid: 'zzzzz-tpzed-abcdefghijklmno',
@@ -66,7 +59,6 @@ class UsersTest < ActionDispatch::IntegrationTest
     # invoke setup on the same user
     post "/arvados/v1/users/setup",
       params: {
-        repo_name: repo_name,
         vm_uuid: virtual_machines(:testvm).uuid,
         uuid: 'zzzzz-tpzed-abcdefghijklmno',
       },
@@ -81,10 +73,7 @@ class UsersTest < ActionDispatch::IntegrationTest
     assert_not_nil created['email'], 'expected non-nil email'
     assert_nil created['identity_url'], 'expected no identity_url'
 
-    # arvados#user, repo link and link add user to 'All users' group
-    verify_link response_items, 'arvados#repository', true, 'permission', 'can_manage',
-        'foo/usertestrepo', created['uuid'], 'arvados#repository', true, 'Repository'
-
+    # arvados#user, and link to add user to 'All users' group
     verify_link response_items, 'arvados#group', true, 'permission', 'can_write',
         'All users', created['uuid'], 'arvados#group', true, 'Group'
 
@@ -119,31 +108,6 @@ class UsersTest < ActionDispatch::IntegrationTest
     verify_link response_items, 'arvados#virtualMachine', false, 'permission', 'can_login',
         nil, created['uuid'], 'arvados#virtualMachine', false, 'VirtualMachine'
 
-   # invoke setup with a repository
-    post "/arvados/v1/users/setup",
-      params: {
-        repo_name: 'newusertestrepo',
-        uuid: created['uuid']
-      },
-      headers: auth(:admin)
-
-    assert_response :success
-
-    response_items = json_response['items']
-    created = find_obj_in_resp response_items, 'arvados#user', nil
-
-    assert_equal 'foo at example.com', created['email'], 'expected input email'
-
-     # verify links
-    verify_link response_items, 'arvados#group', true, 'permission', 'can_write',
-        'All users', created['uuid'], 'arvados#group', true, 'Group'
-
-    verify_link response_items, 'arvados#repository', true, 'permission', 'can_manage',
-        'foo/newusertestrepo', created['uuid'], 'arvados#repository', true, 'Repository'
-
-    verify_link response_items, 'arvados#virtualMachine', false, 'permission', 'can_login',
-        nil, created['uuid'], 'arvados#virtualMachine', false, 'VirtualMachine'
-
     # invoke setup with a vm_uuid
     post "/arvados/v1/users/setup",
       params: {
@@ -173,7 +137,6 @@ class UsersTest < ActionDispatch::IntegrationTest
   test "setup and unsetup user" do
     post "/arvados/v1/users/setup",
       params: {
-        repo_name: 'newusertestrepo',
         vm_uuid: virtual_machines(:testvm).uuid,
         user: {email: 'foo at example.com'},
       },
@@ -185,14 +148,11 @@ class UsersTest < ActionDispatch::IntegrationTest
     assert_not_nil created['uuid'], 'expected uuid for the new user'
     assert_equal created['email'], 'foo at example.com', 'expected given email'
 
-    # four extra links: system_group, login, group, repo and vm
+    # three extra links: system_group, login, group and vm
 
     verify_link response_items, 'arvados#group', true, 'permission', 'can_write',
         'All users', created['uuid'], 'arvados#group', true, 'Group'
 
-    verify_link response_items, 'arvados#repository', true, 'permission', 'can_manage',
-        'foo/newusertestrepo', created['uuid'], 'arvados#repository', true, 'Repository'
-
     verify_link response_items, 'arvados#virtualMachine', true, 'permission', 'can_login',
         virtual_machines(:testvm).uuid, created['uuid'], 'arvados#virtualMachine', false, 'VirtualMachine'
 
@@ -276,13 +236,6 @@ class UsersTest < ActionDispatch::IntegrationTest
     assert_equal(users(:project_viewer).uuid, json_response['owner_uuid'])
     assert_equal(users(:project_viewer).uuid, json_response['authorized_user_uuid'])
 
-    get('/arvados/v1/repositories/' + repositories(:foo).uuid,
-      params: {},
-      headers: auth(:active))
-    assert_response(:success)
-    assert_equal(users(:project_viewer).uuid, json_response['owner_uuid'])
-    assert_equal("#{users(:project_viewer).username}/foo", json_response['name'])
-
     get('/arvados/v1/groups/' + groups(:aproject).uuid,
       params: {},
       headers: auth(:active))
@@ -317,41 +270,6 @@ class UsersTest < ActionDispatch::IntegrationTest
     assert_equal 'barney', json_response['username']
   end
 
-  test 'merge with repository name conflict' do
-    post('/arvados/v1/groups',
-      params: {
-        group: {
-          group_class: 'project',
-          name: "active user's stuff",
-        },
-      },
-      headers: auth(:project_viewer))
-    assert_response(:success)
-    project_uuid = json_response['uuid']
-
-    post('/arvados/v1/repositories/',
-         params: { :repository => { :name => "#{users(:project_viewer).username}/foo", :owner_uuid => users(:project_viewer).uuid } },
-         headers: auth(:project_viewer))
-    assert_response(:success)
-
-    post('/arvados/v1/users/merge',
-      params: {
-        new_user_token: api_client_authorizations(:project_viewer_trustedclient).api_token,
-        new_owner_uuid: project_uuid,
-        redirect_to_new_user: true,
-      },
-      headers: auth(:active_trustedclient))
-    assert_response(:success)
-
-    get('/arvados/v1/repositories/' + repositories(:foo).uuid,
-      params: {},
-      headers: auth(:active))
-    assert_response(:success)
-    assert_equal(users(:project_viewer).uuid, json_response['owner_uuid'])
-    assert_equal("#{users(:project_viewer).username}/migratedfoo", json_response['name'])
-
-  end
-
   test "cannot set is_active to false directly" do
     post('/arvados/v1/users',
       params: {
diff --git a/services/api/test/test.git.tar b/services/api/test/test.git.tar
deleted file mode 100644
index 7af80b0774..0000000000
Binary files a/services/api/test/test.git.tar and /dev/null differ
diff --git a/services/api/test/unit/arvados_model_test.rb b/services/api/test/unit/arvados_model_test.rb
index 69a2710bb9..341a8462dd 100644
--- a/services/api/test/unit/arvados_model_test.rb
+++ b/services/api/test/unit/arvados_model_test.rb
@@ -8,20 +8,20 @@ class ArvadosModelTest < ActiveSupport::TestCase
   fixtures :all
 
   def create_with_attrs attrs
-    a = Specimen.create({material: 'caloric'}.merge(attrs))
+    a = Collection.create({properties: {'foo' => 'bar'}}.merge(attrs))
     a if a.valid?
   end
 
   test 'non-admin cannot assign uuid' do
     set_user_from_auth :active_trustedclient
-    want_uuid = Specimen.generate_uuid
+    want_uuid = Collection.generate_uuid
     a = create_with_attrs(uuid: want_uuid)
     assert_nil a, "Non-admin should not assign uuid."
   end
 
   test 'admin can assign valid uuid' do
     set_user_from_auth :admin_trustedclient
-    want_uuid = Specimen.generate_uuid
+    want_uuid = Collection.generate_uuid
     a = create_with_attrs(uuid: want_uuid)
     assert_equal want_uuid, a.uuid, "Admin should assign valid uuid."
     assert a.uuid.length==27, "Auto assigned uuid length is wrong."
@@ -29,7 +29,7 @@ class ArvadosModelTest < ActiveSupport::TestCase
 
   test 'admin cannot assign uuid with wrong object type' do
     set_user_from_auth :admin_trustedclient
-    want_uuid = Human.generate_uuid
+    want_uuid = Group.generate_uuid
     a = create_with_attrs(uuid: want_uuid)
     assert_nil a, "Admin should not be able to assign invalid uuid."
   end
@@ -126,10 +126,23 @@ class ArvadosModelTest < ActiveSupport::TestCase
   end
 
   test "search index exists on models that go into projects" do
-    all_tables =  ActiveRecord::Base.connection.tables
-    all_tables.delete 'schema_migrations'
-    all_tables.delete 'permission_refresh_lock'
-    all_tables.delete 'ar_internal_metadata'
+    all_tables =  ActiveRecord::Base.connection.tables - [
+      'ar_internal_metadata',
+      'permission_refresh_lock',
+      'schema_migrations',
+      # tables that may still exist in the database even though model
+      # classes have been deleted:
+      'humans',
+      'jobs',
+      'job_tasks',
+      'keep_disks',
+      'nodes',
+      'pipeline_instances',
+      'pipeline_templates',
+      'repositories',
+      'specimens',
+      'traits',
+    ]
 
     all_tables.each do |table|
       table_class = table.classify.constantize
@@ -159,9 +172,6 @@ class ArvadosModelTest < ActiveSupport::TestCase
     %w[collections collections_trgm_text_search_idx],
     %w[container_requests container_requests_trgm_text_search_idx],
     %w[groups groups_trgm_text_search_idx],
-    %w[jobs jobs_trgm_text_search_idx],
-    %w[pipeline_instances pipeline_instances_trgm_text_search_idx],
-    %w[pipeline_templates pipeline_templates_trgm_text_search_idx],
     %w[workflows workflows_trgm_text_search_idx]
   ].each do |model|
     table = model[0]
@@ -180,25 +190,25 @@ class ArvadosModelTest < ActiveSupport::TestCase
   end
 
   test "selectable_attributes includes database attributes" do
-    assert_includes(Job.selectable_attributes, "success")
+    assert_includes(Collection.selectable_attributes, "name")
   end
 
   test "selectable_attributes includes non-database attributes" do
-    assert_includes(Job.selectable_attributes, "node_uuids")
+    assert_includes(Collection.selectable_attributes, "unsigned_manifest_text")
   end
 
   test "selectable_attributes includes common attributes in extensions" do
-    assert_includes(Job.selectable_attributes, "uuid")
+    assert_includes(Collection.selectable_attributes, "uuid")
   end
 
   test "selectable_attributes does not include unexposed attributes" do
-    refute_includes(Job.selectable_attributes, "nodes")
+    refute_includes(Collection.selectable_attributes, "id")
   end
 
   test "selectable_attributes on a non-default template" do
-    attr_a = Job.selectable_attributes(:common)
+    attr_a = Collection.selectable_attributes(:common)
     assert_includes(attr_a, "uuid")
-    refute_includes(attr_a, "success")
+    refute_includes(attr_a, "name")
   end
 
   test 'create and retrieve using created_at time' do
@@ -260,19 +270,21 @@ class ArvadosModelTest < ActiveSupport::TestCase
       else
         Rails.configuration.AuditLogs.MaxAge = 0
       end
+      tested_serialized = false
       [
         User.find_by_uuid(users(:active).uuid),
         ContainerRequest.find_by_uuid(container_requests(:queued).uuid),
         Container.find_by_uuid(containers(:queued).uuid),
-        PipelineInstance.find_by_uuid(pipeline_instances(:has_component_with_completed_jobs).uuid),
-        PipelineTemplate.find_by_uuid(pipeline_templates(:two_part).uuid),
-        Job.find_by_uuid(jobs(:running).uuid)
+        Group.find_by_uuid(groups(:afiltergroup).uuid),
+        Collection.find_by_uuid(collections(:collection_with_one_property).uuid),
       ].each do |obj|
-        assert_not(obj.class.serialized_attributes.empty?,
-          "#{obj.class} model doesn't have serialized attributes")
+        if !obj.class.serialized_attributes.empty?
+          tested_serialized = true
+        end
         # obj shouldn't have changed since it's just retrieved from the database
         assert_not(obj.changed?, "#{obj.class} model's attribute(s) appear as changed: '#{obj.changes.keys.join(',')}' with audit logs #{auditlogs_enabled ? '': 'not '}enabled.")
       end
+      assert(tested_serialized, "did not test any models with serialized attributes")
     end
   end
 end
diff --git a/services/api/test/unit/commit_ancestor_test.rb b/services/api/test/unit/commit_ancestor_test.rb
deleted file mode 100644
index 46041211bb..0000000000
--- a/services/api/test/unit/commit_ancestor_test.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class CommitAncestorTest < ActiveSupport::TestCase
-  # test "the truth" do
-  #   assert true
-  # end
-end
diff --git a/services/api/test/unit/commit_test.rb b/services/api/test/unit/commit_test.rb
deleted file mode 100644
index e83061f61a..0000000000
--- a/services/api/test/unit/commit_test.rb
+++ /dev/null
@@ -1,270 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-require 'helpers/git_test_helper'
-
-# NOTE: calling Commit.find_commit_range(nil, nil, 'rev')
-# produces an error message "fatal: bad object 'rev'" on stderr if
-# 'rev' does not exist in a given repository.  Many of these tests
-# report such errors; their presence does not represent a fatal
-# condition.
-
-class CommitTest < ActiveSupport::TestCase
-  # See git_setup.rb for the commit log for test.git.tar
-  include GitTestHelper
-
-  setup do
-    authorize_with :active
-  end
-
-  test 'find_commit_range does not bypass permissions' do
-    authorize_with :inactive
-    assert_raises ArgumentError do
-      CommitsHelper::find_commit_range 'foo', nil, 'main', []
-    end
-  end
-
-  def must_pipe(cmd)
-    begin
-      return IO.read("|#{cmd}")
-    ensure
-      assert $?.success?
-    end
-  end
-
-  [
-   'https://github.com/arvados/arvados.git',
-   'http://github.com/arvados/arvados.git',
-   'git://github.com/arvados/arvados.git',
-  ].each do |url|
-    test "find_commit_range uses fetch_remote_repository to get #{url}" do
-      fake_gitdir = repositories(:foo).server_path
-      CommitsHelper::expects(:cache_dir_for).once.with(url).returns fake_gitdir
-      CommitsHelper::expects(:fetch_remote_repository).once.with(fake_gitdir, url).returns true
-      c = CommitsHelper::find_commit_range url, nil, 'main', []
-      refute_empty c
-    end
-  end
-
-  [
-   'bogus/repo',
-   '/bogus/repo',
-   '/not/allowed/.git',
-   'file:///not/allowed.git',
-   'git.arvados.org/arvados.git',
-   'github.com/arvados/arvados.git',
-  ].each do |url|
-    test "find_commit_range skips fetch_remote_repository for #{url}" do
-      CommitsHelper::expects(:fetch_remote_repository).never
-      assert_raises ArgumentError do
-        CommitsHelper::find_commit_range url, nil, 'main', []
-      end
-    end
-  end
-
-  test 'fetch_remote_repository does not leak commits across repositories' do
-    url = "http://localhost:1/fake/fake.git"
-    fetch_remote_from_local_repo url, :foo
-    c = CommitsHelper::find_commit_range url, nil, 'main', []
-    assert_equal ['077ba2ad3ea24a929091a9e6ce545c93199b8e57'], c
-
-    url = "http://localhost:2/fake/fake.git"
-    fetch_remote_from_local_repo url, 'file://' + File.expand_path('../../.git', Rails.root)
-    c = CommitsHelper::find_commit_range url, nil, '077ba2ad3ea24a929091a9e6ce545c93199b8e57', []
-    assert_equal [], c
-  end
-
-  test 'tag_in_internal_repository creates and updates tags in internal.git' do
-    authorize_with :active
-    gitint = "git --git-dir #{Rails.configuration.Containers.JobsAPI.GitInternalDir}"
-    IO.read("|#{gitint} tag -d testtag 2>/dev/null") # "no such tag", fine
-    assert_match(/^fatal: /, IO.read("|#{gitint} show testtag 2>&1"))
-    refute $?.success?
-    CommitsHelper::tag_in_internal_repository 'active/foo', '31ce37fe365b3dc204300a3e4c396ad333ed0556', 'testtag'
-    assert_match(/^commit 31ce37f/, IO.read("|#{gitint} show testtag"))
-    assert $?.success?
-  end
-
-  def with_foo_repository
-    Dir.chdir("#{Rails.configuration.Git.Repositories}/#{repositories(:foo).uuid}") do
-      must_pipe("git checkout main 2>&1")
-      yield
-    end
-  end
-
-  test 'tag_in_internal_repository, new non-tip sha1 in local repo' do
-    tag = "tag#{rand(10**10)}"
-    sha1 = nil
-    with_foo_repository do
-      must_pipe("git checkout -b branch-#{rand(10**10)} 2>&1")
-      must_pipe("echo -n #{tag.shellescape} >bar")
-      must_pipe("git add bar")
-      must_pipe("git -c user.email=x at x -c user.name=X commit -m -")
-      sha1 = must_pipe("git log -n1 --format=%H").strip
-      must_pipe("git rm bar")
-      must_pipe("git -c user.email=x at x -c user.name=X commit -m -")
-    end
-    CommitsHelper::tag_in_internal_repository 'active/foo', sha1, tag
-    gitint = "git --git-dir #{Rails.configuration.Containers.JobsAPI.GitInternalDir.shellescape}"
-    assert_match(/^commit /, IO.read("|#{gitint} show #{tag.shellescape}"))
-    assert $?.success?
-  end
-
-  test 'tag_in_internal_repository, new unreferenced sha1 in local repo' do
-    tag = "tag#{rand(10**10)}"
-    sha1 = nil
-    with_foo_repository do
-      must_pipe("echo -n #{tag.shellescape} >bar")
-      must_pipe("git add bar")
-      must_pipe("git -c user.email=x at x -c user.name=X commit -m -")
-      sha1 = must_pipe("git log -n1 --format=%H").strip
-      must_pipe("git reset --hard HEAD^")
-    end
-    CommitsHelper::tag_in_internal_repository 'active/foo', sha1, tag
-    gitint = "git --git-dir #{Rails.configuration.Containers.JobsAPI.GitInternalDir.shellescape}"
-    assert_match(/^commit /, IO.read("|#{gitint} show #{tag.shellescape}"))
-    assert $?.success?
-  end
-
-  # In active/shabranchnames, "7387838c69a21827834586cc42b467ff6c63293b" is
-  # both a commit hash, and the name of a branch that begins from that same
-  # commit.
-  COMMIT_BRANCH_NAME = "7387838c69a21827834586cc42b467ff6c63293b"
-  # A commit that appears in the branch after 7387838c.
-  COMMIT_BRANCH_COMMIT_2 = "abec49829bf1758413509b7ffcab32a771b71e81"
-  # "738783" is another branch that starts from the above commit.
-  SHORT_COMMIT_BRANCH_NAME = COMMIT_BRANCH_NAME[0, 6]
-  # A commit that appears in branch 738783 after 7387838c.
-  SHORT_BRANCH_COMMIT_2 = "77e1a93093663705a63bb4d505698047e109dedd"
-
-  test "find_commit_range min_version prefers commits over branch names" do
-    assert_equal([COMMIT_BRANCH_NAME],
-                 CommitsHelper::find_commit_range("active/shabranchnames",
-                                          COMMIT_BRANCH_NAME, nil, nil))
-  end
-
-  test "find_commit_range max_version prefers commits over branch names" do
-    assert_equal([COMMIT_BRANCH_NAME],
-                 CommitsHelper::find_commit_range("active/shabranchnames",
-                                          nil, COMMIT_BRANCH_NAME, nil))
-  end
-
-  test "find_commit_range min_version with short branch name" do
-    assert_equal([SHORT_BRANCH_COMMIT_2],
-                 CommitsHelper::find_commit_range("active/shabranchnames",
-                                          SHORT_COMMIT_BRANCH_NAME, nil, nil))
-  end
-
-  test "find_commit_range max_version with short branch name" do
-    assert_equal([SHORT_BRANCH_COMMIT_2],
-                 CommitsHelper::find_commit_range("active/shabranchnames",
-                                          nil, SHORT_COMMIT_BRANCH_NAME, nil))
-  end
-
-  test "find_commit_range min_version with disambiguated branch name" do
-    assert_equal([COMMIT_BRANCH_COMMIT_2],
-                 CommitsHelper::find_commit_range("active/shabranchnames",
-                                          "heads/#{COMMIT_BRANCH_NAME}",
-                                          nil, nil))
-  end
-
-  test "find_commit_range max_version with disambiguated branch name" do
-    assert_equal([COMMIT_BRANCH_COMMIT_2],
-                 CommitsHelper::find_commit_range("active/shabranchnames", nil,
-                                          "heads/#{COMMIT_BRANCH_NAME}", nil))
-  end
-
-  test "find_commit_range min_version with unambiguous short name" do
-    assert_equal([COMMIT_BRANCH_NAME],
-                 CommitsHelper::find_commit_range("active/shabranchnames",
-                                          COMMIT_BRANCH_NAME[0..-2], nil, nil))
-  end
-
-  test "find_commit_range max_version with unambiguous short name" do
-    assert_equal([COMMIT_BRANCH_NAME],
-                 CommitsHelper::find_commit_range("active/shabranchnames", nil,
-                                          COMMIT_BRANCH_NAME[0..-2], nil))
-  end
-
-  test "find_commit_range laundry list" do
-    authorize_with :active
-
-    # single
-    a = CommitsHelper::find_commit_range('active/foo', nil, '31ce37fe365b3dc204300a3e4c396ad333ed0556', nil)
-    assert_equal ['31ce37fe365b3dc204300a3e4c396ad333ed0556'], a
-
-    #test "test_branch1" do
-    a = CommitsHelper::find_commit_range('active/foo', nil, 'main', nil)
-    assert_includes(a, '077ba2ad3ea24a929091a9e6ce545c93199b8e57')
-
-    #test "test_branch2" do
-    a = CommitsHelper::find_commit_range('active/foo', nil, 'b1', nil)
-    assert_equal ['1de84a854e2b440dc53bf42f8548afa4c17da332'], a
-
-    #test "test_branch3" do
-    a = CommitsHelper::find_commit_range('active/foo', nil, 'HEAD', nil)
-    assert_equal ['1de84a854e2b440dc53bf42f8548afa4c17da332'], a
-
-    #test "test_single_revision_repo" do
-    a = CommitsHelper::find_commit_range('active/foo', nil, '31ce37fe365b3dc204300a3e4c396ad333ed0556', nil)
-    assert_equal ['31ce37fe365b3dc204300a3e4c396ad333ed0556'], a
-    a = CommitsHelper::find_commit_range('arvados', nil, '31ce37fe365b3dc204300a3e4c396ad333ed0556', nil)
-    assert_equal [], a
-
-    #test "test_multi_revision" do
-    # complains "fatal: bad object 077ba2ad3ea24a929091a9e6ce545c93199b8e57"
-    a = CommitsHelper::find_commit_range('active/foo', '31ce37fe365b3dc204300a3e4c396ad333ed0556', '077ba2ad3ea24a929091a9e6ce545c93199b8e57', nil)
-    assert_equal ['077ba2ad3ea24a929091a9e6ce545c93199b8e57', '4fe459abe02d9b365932b8f5dc419439ab4e2577', '31ce37fe365b3dc204300a3e4c396ad333ed0556'], a
-
-    #test "test_tag" do
-    # complains "fatal: ambiguous argument 'tag1': unknown revision or path
-    # not in the working tree."
-    a = CommitsHelper::find_commit_range('active/foo', 'tag1', 'main', nil)
-    assert_equal ['077ba2ad3ea24a929091a9e6ce545c93199b8e57', '4fe459abe02d9b365932b8f5dc419439ab4e2577'], a
-
-    #test "test_multi_revision_exclude" do
-    a = CommitsHelper::find_commit_range('active/foo', '31ce37fe365b3dc204300a3e4c396ad333ed0556', '077ba2ad3ea24a929091a9e6ce545c93199b8e57', ['4fe459abe02d9b365932b8f5dc419439ab4e2577'])
-    assert_equal ['077ba2ad3ea24a929091a9e6ce545c93199b8e57', '31ce37fe365b3dc204300a3e4c396ad333ed0556'], a
-
-    #test "test_multi_revision_tagged_exclude" do
-    # complains "fatal: bad object 077ba2ad3ea24a929091a9e6ce545c93199b8e57"
-    a = CommitsHelper::find_commit_range('active/foo', '31ce37fe365b3dc204300a3e4c396ad333ed0556', '077ba2ad3ea24a929091a9e6ce545c93199b8e57', ['tag1'])
-    assert_equal ['077ba2ad3ea24a929091a9e6ce545c93199b8e57', '31ce37fe365b3dc204300a3e4c396ad333ed0556'], a
-
-    Dir.mktmpdir do |touchdir|
-      # invalid input to maximum
-      a = CommitsHelper::find_commit_range('active/foo', nil, "31ce37fe365b3dc204300a3e4c396ad333ed0556 ; touch #{touchdir}/uh_oh", nil)
-      assert !File.exist?("#{touchdir}/uh_oh"), "#{touchdir}/uh_oh should not exist, 'maximum' parameter of find_commit_range is exploitable"
-      assert_equal [], a
-
-      # invalid input to maximum
-      a = CommitsHelper::find_commit_range('active/foo', nil, "$(uname>#{touchdir}/uh_oh)", nil)
-      assert !File.exist?("#{touchdir}/uh_oh"), "#{touchdir}/uh_oh should not exist, 'maximum' parameter of find_commit_range is exploitable"
-      assert_equal [], a
-
-      # invalid input to minimum
-      a = CommitsHelper::find_commit_range('active/foo', "31ce37fe365b3dc204300a3e4c396ad333ed0556 ; touch #{touchdir}/uh_oh", "31ce37fe365b3dc204300a3e4c396ad333ed0556", nil)
-      assert !File.exist?("#{touchdir}/uh_oh"), "#{touchdir}/uh_oh should not exist, 'minimum' parameter of find_commit_range is exploitable"
-      assert_equal [], a
-
-      # invalid input to minimum
-      a = CommitsHelper::find_commit_range('active/foo', "$(uname>#{touchdir}/uh_oh)", "31ce37fe365b3dc204300a3e4c396ad333ed0556", nil)
-      assert !File.exist?("#{touchdir}/uh_oh"), "#{touchdir}/uh_oh should not exist, 'minimum' parameter of find_commit_range is exploitable"
-      assert_equal [], a
-
-      # invalid input to 'excludes'
-      # complains "fatal: bad object 077ba2ad3ea24a929091a9e6ce545c93199b8e57"
-      a = CommitsHelper::find_commit_range('active/foo', "31ce37fe365b3dc204300a3e4c396ad333ed0556", "077ba2ad3ea24a929091a9e6ce545c93199b8e57", ["4fe459abe02d9b365932b8f5dc419439ab4e2577 ; touch #{touchdir}/uh_oh"])
-      assert !File.exist?("#{touchdir}/uh_oh"), "#{touchdir}/uh_oh should not exist, 'excludes' parameter of find_commit_range is exploitable"
-      assert_equal [], a
-
-      # invalid input to 'excludes'
-      # complains "fatal: bad object 077ba2ad3ea24a929091a9e6ce545c93199b8e57"
-      a = CommitsHelper::find_commit_range('active/foo', "31ce37fe365b3dc204300a3e4c396ad333ed0556", "077ba2ad3ea24a929091a9e6ce545c93199b8e57", ["$(uname>#{touchdir}/uh_oh)"])
-      assert !File.exist?("#{touchdir}/uh_oh"), "#{touchdir}/uh_oh should not exist, 'excludes' parameter of find_commit_range is exploitable"
-      assert_equal [], a
-    end
-  end
-end
diff --git a/services/api/test/unit/container_test.rb b/services/api/test/unit/container_test.rb
index 09b885b391..e7a12a6387 100644
--- a/services/api/test/unit/container_test.rb
+++ b/services/api/test/unit/container_test.rb
@@ -333,7 +333,7 @@ class ContainerTest < ActiveSupport::TestCase
     set_user_from_auth :dispatch1
 
     out1 = '1f4b0bc7583c2a7f9102c395f4ffc5e3+45'
-    log1 = collections(:real_log_collection).portable_data_hash
+    log1 = collections(:log_collection).portable_data_hash
     c_output1.update!({state: Container::Locked})
     c_output1.update!({state: Container::Running})
     c_output1.update!(completed_attrs.merge({log: log1, output: out1}))
@@ -845,7 +845,7 @@ class ContainerTest < ActiveSupport::TestCase
     assert c.lock, show_errors(c)
     assert c.update(
              state: Container::Cancelled,
-             log: collections(:real_log_collection).portable_data_hash,
+             log: collections(:log_collection).portable_data_hash,
            ), show_errors(c)
     check_no_change_from_cancelled c
   end
@@ -933,7 +933,7 @@ class ContainerTest < ActiveSupport::TestCase
 
   test "locked_by_uuid can update log when locked/running, and output when running" do
     set_user_from_auth :active
-    logcoll = collections(:real_log_collection)
+    logcoll = collections(:container_log_collection)
     c, cr1 = minimal_new
     cr2 = ContainerRequest.new(DEFAULT_ATTRS)
     cr2.state = ContainerRequest::Committed
@@ -975,8 +975,8 @@ class ContainerTest < ActiveSupport::TestCase
     assert_equal cr1log_uuid, cr1.log_uuid
     assert_equal cr2log_uuid, cr2.log_uuid
     assert_equal 1, Collection.where(uuid: [cr1log_uuid, cr2log_uuid]).to_a.collect(&:portable_data_hash).uniq.length
-    assert_equal ". acbd18db4cc2f85cedef654fccc4a4d8+3 cdd549ae79fe6640fa3d5c6261d8303c+195 0:3:foo.txt 3:195:zzzzz-8i9sb-0vsrcqi7whchuil.log.txt
-./log\\040for\\040container\\040#{cr1.container_uuid} acbd18db4cc2f85cedef654fccc4a4d8+3 cdd549ae79fe6640fa3d5c6261d8303c+195 0:3:foo.txt 3:195:zzzzz-8i9sb-0vsrcqi7whchuil.log.txt
+    assert_equal ". 8c12f5f5297b7337598170c6f531fcee+7882 acbd18db4cc2f85cedef654fccc4a4d8+3 0:0:arv-mount.txt 0:1910:container.json 1910:1264:crunch-run.txt 3174:1005:crunchstat.txt 7882:3:foo.txt 4179:659:hoststat.txt 4838:2811:node-info.txt 7649:233:node.json 0:0:stderr.txt
+./log\\040for\\040container\\040#{cr1.container_uuid} 8c12f5f5297b7337598170c6f531fcee+7882 acbd18db4cc2f85cedef654fccc4a4d8+3 0:0:arv-mount.txt 0:1910:container.json 1910:1264:crunch-run.txt 3174:1005:crunchstat.txt 7882:3:foo.txt 4179:659:hoststat.txt 4838:2811:node-info.txt 7649:233:node.json 0:0:stderr.txt
 ", Collection.find_by_uuid(cr1log_uuid).manifest_text
   end
 
@@ -1013,7 +1013,7 @@ class ContainerTest < ActiveSupport::TestCase
       assert c.update(runtime_status: {'warning' => 'something happened'})
       assert c.update(progress: 0.5)
       assert c.update(exit_code: 0)
-      refute c.update(log: collections(:real_log_collection).portable_data_hash)
+      refute c.update(log: collections(:log_collection).portable_data_hash)
       c.reload
       assert c.update(state: Container::Complete, exit_code: 0)
     end
diff --git a/services/api/test/unit/group_test.rb b/services/api/test/unit/group_test.rb
index 36f42006ff..e03ca8da05 100644
--- a/services/api/test/unit/group_test.rb
+++ b/services/api/test/unit/group_test.rb
@@ -18,13 +18,13 @@ class GroupTest < ActiveSupport::TestCase
     assert g.save, "active user should be able to modify group #{g.uuid}"
 
     # Use the group as the owner of a new object
-    s = Specimen.
+    s = Collection.
       create(owner_uuid: groups(:bad_group_has_ownership_cycle_b).uuid)
     assert s.valid?, "ownership should pass validation #{s.errors.messages}"
     assert_equal false, s.save, "should not save object with #{g.uuid} as owner"
 
     # Use the group as the new owner of an existing object
-    s = specimens(:in_aproject)
+    s = collections(:collection_owned_by_active)
     s.owner_uuid = groups(:bad_group_has_ownership_cycle_b).uuid
     assert s.valid?, "ownership should pass validation"
     assert_equal false, s.save, "should not save object with #{g.uuid} as owner"
diff --git a/services/api/test/unit/helpers/api_client_authorizations_helper_test.rb b/services/api/test/unit/helpers/api_client_authorizations_helper_test.rb
deleted file mode 100644
index 01ed4302da..0000000000
--- a/services/api/test/unit/helpers/api_client_authorizations_helper_test.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class ApiClientAuthorizationsHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/helpers/api_clients_helper_test.rb b/services/api/test/unit/helpers/api_clients_helper_test.rb
deleted file mode 100644
index 4901fb45df..0000000000
--- a/services/api/test/unit/helpers/api_clients_helper_test.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class ApiClientsHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/helpers/authorized_keys_helper_test.rb b/services/api/test/unit/helpers/authorized_keys_helper_test.rb
deleted file mode 100644
index 010a0fe453..0000000000
--- a/services/api/test/unit/helpers/authorized_keys_helper_test.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class AuthorizedKeysHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/helpers/collections_helper_test.rb b/services/api/test/unit/helpers/collections_helper_test.rb
deleted file mode 100644
index dd01ca7b82..0000000000
--- a/services/api/test/unit/helpers/collections_helper_test.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class CollectionsHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/helpers/commit_ancestors_helper_test.rb b/services/api/test/unit/helpers/commit_ancestors_helper_test.rb
deleted file mode 100644
index 423dbf6769..0000000000
--- a/services/api/test/unit/helpers/commit_ancestors_helper_test.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class CommitAncestorsHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/helpers/commits_helper_test.rb b/services/api/test/unit/helpers/commits_helper_test.rb
deleted file mode 100644
index fd960a86f3..0000000000
--- a/services/api/test/unit/helpers/commits_helper_test.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class CommitsHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/helpers/groups_helper_test.rb b/services/api/test/unit/helpers/groups_helper_test.rb
deleted file mode 100644
index ce7a3fad2b..0000000000
--- a/services/api/test/unit/helpers/groups_helper_test.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class GroupsHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/helpers/humans_helper_test.rb b/services/api/test/unit/helpers/humans_helper_test.rb
deleted file mode 100644
index 22f9e819ce..0000000000
--- a/services/api/test/unit/helpers/humans_helper_test.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class HumansHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/helpers/job_tasks_helper_test.rb b/services/api/test/unit/helpers/job_tasks_helper_test.rb
deleted file mode 100644
index af0302ccf3..0000000000
--- a/services/api/test/unit/helpers/job_tasks_helper_test.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class JobTasksHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/helpers/jobs_helper_test.rb b/services/api/test/unit/helpers/jobs_helper_test.rb
deleted file mode 100644
index 9d64b7d832..0000000000
--- a/services/api/test/unit/helpers/jobs_helper_test.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class JobsHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/helpers/keep_disks_helper_test.rb b/services/api/test/unit/helpers/keep_disks_helper_test.rb
deleted file mode 100644
index 9dcc619df5..0000000000
--- a/services/api/test/unit/helpers/keep_disks_helper_test.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class KeepDisksHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/helpers/links_helper_test.rb b/services/api/test/unit/helpers/links_helper_test.rb
deleted file mode 100644
index 918f145ff6..0000000000
--- a/services/api/test/unit/helpers/links_helper_test.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class LinksHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/helpers/logs_helper_test.rb b/services/api/test/unit/helpers/logs_helper_test.rb
deleted file mode 100644
index 616f6e664b..0000000000
--- a/services/api/test/unit/helpers/logs_helper_test.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class LogsHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/helpers/nodes_helper_test.rb b/services/api/test/unit/helpers/nodes_helper_test.rb
deleted file mode 100644
index 8a92eb990d..0000000000
--- a/services/api/test/unit/helpers/nodes_helper_test.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class NodesHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/helpers/pipeline_instances_helper_test.rb b/services/api/test/unit/helpers/pipeline_instances_helper_test.rb
deleted file mode 100644
index 9d3b5c48f1..0000000000
--- a/services/api/test/unit/helpers/pipeline_instances_helper_test.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class PipelineInstancesHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/helpers/pipeline_templates_helper_test.rb b/services/api/test/unit/helpers/pipeline_templates_helper_test.rb
deleted file mode 100644
index 9a9a4179d6..0000000000
--- a/services/api/test/unit/helpers/pipeline_templates_helper_test.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class PipelinesHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/helpers/repositories_helper_test.rb b/services/api/test/unit/helpers/repositories_helper_test.rb
deleted file mode 100644
index 33cb590513..0000000000
--- a/services/api/test/unit/helpers/repositories_helper_test.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class RepositoriesHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/helpers/specimens_helper_test.rb b/services/api/test/unit/helpers/specimens_helper_test.rb
deleted file mode 100644
index 3709198065..0000000000
--- a/services/api/test/unit/helpers/specimens_helper_test.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class SpecimensHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/helpers/traits_helper_test.rb b/services/api/test/unit/helpers/traits_helper_test.rb
deleted file mode 100644
index 03b6a97f41..0000000000
--- a/services/api/test/unit/helpers/traits_helper_test.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class TraitsHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/helpers/virtual_machines_helper_test.rb b/services/api/test/unit/helpers/virtual_machines_helper_test.rb
deleted file mode 100644
index 99fc258cb4..0000000000
--- a/services/api/test/unit/helpers/virtual_machines_helper_test.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class VirtualMachinesHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/human_test.rb b/services/api/test/unit/human_test.rb
deleted file mode 100644
index 83cc40e686..0000000000
--- a/services/api/test/unit/human_test.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class HumanTest < ActiveSupport::TestCase
-  # test "the truth" do
-  #   assert true
-  # end
-end
diff --git a/services/api/test/unit/job_task_test.rb b/services/api/test/unit/job_task_test.rb
deleted file mode 100644
index 36a0e723f2..0000000000
--- a/services/api/test/unit/job_task_test.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class JobTaskTest < ActiveSupport::TestCase
-
-end
diff --git a/services/api/test/unit/job_test.rb b/services/api/test/unit/job_test.rb
deleted file mode 100644
index 815079f8af..0000000000
--- a/services/api/test/unit/job_test.rb
+++ /dev/null
@@ -1,277 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-require 'helpers/git_test_helper'
-require 'helpers/docker_migration_helper'
-
-class JobTest < ActiveSupport::TestCase
-  include DockerMigrationHelper
-  include GitTestHelper
-
-  BAD_COLLECTION = "#{'f' * 32}+0"
-
-  setup do
-    set_user_from_auth :active
-  end
-
-  def job_attrs merge_me={}
-    # Default (valid) set of attributes, with given overrides
-    {
-      script: "hash",
-      script_version: "main",
-      repository: "active/foo",
-    }.merge(merge_me)
-  end
-
-  test "Job without Docker image doesn't get locator" do
-    job = Job.new job_attrs
-    assert job.valid?, job.errors.full_messages.to_s
-    assert_nil job.docker_image_locator
-  end
-
-  { 'name' => [:links, :docker_image_collection_tag, :name],
-    'hash' => [:links, :docker_image_collection_hash, :name],
-    'locator' => [:collections, :docker_image, :portable_data_hash],
-  }.each_pair do |spec_type, (fixture_type, fixture_name, fixture_attr)|
-    test "Job initialized with Docker image #{spec_type} gets locator" do
-      image_spec = send(fixture_type, fixture_name).send(fixture_attr)
-      job = Job.new job_attrs(runtime_constraints:
-                              {'docker_image' => image_spec})
-      assert job.valid?, job.errors.full_messages.to_s
-      assert_equal(collections(:docker_image).portable_data_hash, job.docker_image_locator)
-    end
-
-    test "Job modified with Docker image #{spec_type} gets locator" do
-      job = Job.new job_attrs
-      assert job.valid?, job.errors.full_messages.to_s
-      assert_nil job.docker_image_locator
-      image_spec = send(fixture_type, fixture_name).send(fixture_attr)
-      job.runtime_constraints['docker_image'] = image_spec
-      assert job.valid?, job.errors.full_messages.to_s
-      assert_equal(collections(:docker_image).portable_data_hash, job.docker_image_locator)
-    end
-  end
-
-  test "removing a Docker runtime constraint removes the locator" do
-    image_locator = collections(:docker_image).portable_data_hash
-    job = Job.new job_attrs(runtime_constraints:
-                            {'docker_image' => image_locator})
-    assert job.valid?, job.errors.full_messages.to_s
-    assert_equal(image_locator, job.docker_image_locator)
-    job.runtime_constraints = {}
-    assert job.valid?, job.errors.full_messages.to_s + "after clearing runtime constraints"
-    assert_nil job.docker_image_locator
-  end
-
-  test "locate a Docker image with a repository + tag" do
-    image_repo, image_tag =
-      links(:docker_image_collection_tag2).name.split(':', 2)
-    job = Job.new job_attrs(runtime_constraints:
-                            {'docker_image' => image_repo,
-                              'docker_image_tag' => image_tag})
-    assert job.valid?, job.errors.full_messages.to_s
-    assert_equal(collections(:docker_image).portable_data_hash, job.docker_image_locator)
-  end
-
-  test "can't locate a Docker image with a nonexistent tag" do
-    image_repo = links(:docker_image_collection_tag).name
-    image_tag = '__nonexistent tag__'
-    job = Job.new job_attrs(runtime_constraints:
-                            {'docker_image' => image_repo,
-                              'docker_image_tag' => image_tag})
-    assert(job.invalid?, "Job with bad Docker tag valid")
-  end
-
-  [
-    false,
-    true
-  ].each do |use_config|
-    test "Job with no Docker image uses default docker image when configuration is set #{use_config}" do
-      default_docker_image = collections(:docker_image)[:portable_data_hash]
-      Rails.configuration.Containers.JobsAPI.DefaultDockerImage = default_docker_image if use_config
-
-      job = Job.new job_attrs
-      assert job.valid?, job.errors.full_messages.to_s
-
-      if use_config
-        refute_nil job.docker_image_locator
-        assert_equal default_docker_image, job.docker_image_locator
-      else
-        assert_nil job.docker_image_locator
-      end
-    end
-  end
-
-  test "locate a Docker image with a partial hash" do
-    image_hash = links(:docker_image_collection_hash).name[0..24]
-    job = Job.new job_attrs(runtime_constraints:
-                            {'docker_image' => image_hash})
-    assert job.valid?, job.errors.full_messages.to_s + " with partial hash #{image_hash}"
-    assert_equal(collections(:docker_image).portable_data_hash, job.docker_image_locator)
-  end
-
-  { 'name' => 'arvados_test_nonexistent',
-    'hash' => 'f' * 64,
-    'locator' => BAD_COLLECTION,
-  }.each_pair do |spec_type, image_spec|
-    test "Job validation fails with nonexistent Docker image #{spec_type}" do
-      Rails.configuration.RemoteClusters = ConfigLoader.to_OrderedOptions({})
-      job = Job.new job_attrs(runtime_constraints:
-                              {'docker_image' => image_spec})
-      assert(job.invalid?, "nonexistent Docker image #{spec_type} #{image_spec} was valid")
-    end
-  end
-
-  test "Job validation fails with non-Docker Collection constraint" do
-    job = Job.new job_attrs(runtime_constraints:
-                            {'docker_image' => collections(:foo_file).uuid})
-    assert(job.invalid?, "non-Docker Collection constraint was valid")
-  end
-
-  test "can create Job with Docker image Collection without Docker links" do
-    image_uuid = collections(:unlinked_docker_image).portable_data_hash
-    job = Job.new job_attrs(runtime_constraints: {"docker_image" => image_uuid})
-    assert(job.valid?, "Job created with unlinked Docker image was invalid")
-    assert_equal(image_uuid, job.docker_image_locator)
-  end
-
-  def check_attrs_unset(job, attrs)
-    assert_empty(attrs.each_key.map { |key| job.send(key) }.compact,
-                 "job has values for #{attrs.keys}")
-  end
-
-  def check_creation_prohibited(attrs)
-    begin
-      job = Job.new(job_attrs(attrs))
-    rescue ActiveModel::MassAssignmentSecurity::Error
-      # Test passes - expected attribute protection
-    else
-      check_attrs_unset(job, attrs)
-    end
-  end
-
-  def check_modification_prohibited(attrs)
-    job = Job.new(job_attrs)
-    attrs.each_pair do |key, value|
-      assert_raises(NoMethodError) { job.send("{key}=".to_sym, value) }
-    end
-    check_attrs_unset(job, attrs)
-  end
-
-  test "can't create Job with Docker image locator" do
-    check_creation_prohibited(docker_image_locator: BAD_COLLECTION)
-  end
-
-  test "can't assign Docker image locator to Job" do
-    check_modification_prohibited(docker_image_locator: BAD_COLLECTION)
-  end
-
-  SDK_MASTER = "ca68b24e51992e790f29df5cc4bc54ce1da4a1c2"
-  SDK_TAGGED = "00634b2b8a492d6f121e3cf1d6587b821136a9a7"
-
-  def sdk_constraint(version)
-    {runtime_constraints: {
-        "arvados_sdk_version" => version,
-        "docker_image" => links(:docker_image_collection_tag).name,
-      }}
-  end
-
-  def check_job_sdk_version(expected)
-    job = yield
-    if expected.nil?
-      refute(job.valid?, "job valid with bad Arvados SDK version")
-    else
-      assert(job.valid?, "job not valid with good Arvados SDK version")
-      assert_equal(expected, job.arvados_sdk_version)
-    end
-  end
-
-  test "can't create job with SDK version assigned directly" do
-    check_creation_prohibited(arvados_sdk_version: SDK_MASTER)
-  end
-
-  test "can't modify job to assign SDK version directly" do
-    check_modification_prohibited(arvados_sdk_version: SDK_MASTER)
-  end
-
-  test 'script_parameters_digest is independent of key order' do
-    j1 = Job.new(job_attrs(script_parameters: {'a' => 'a', 'ddee' => {'d' => 'd', 'e' => 'e'}}))
-    j2 = Job.new(job_attrs(script_parameters: {'ddee' => {'e' => 'e', 'd' => 'd'}, 'a' => 'a'}))
-    assert j1.valid?
-    assert j2.valid?
-    assert_equal(j1.script_parameters_digest, j2.script_parameters_digest)
-  end
-
-  test 'job fixtures have correct script_parameters_digest' do
-    Job.all.each do |j|
-      d = j.script_parameters_digest
-      assert_equal(j.update_script_parameters_digest, d,
-                   "wrong script_parameters_digest for #{j.uuid}")
-    end
-  end
-
-  test 'deep_sort_hash on array of hashes' do
-    a = {'z' => [[{'a' => 'a', 'b' => 'b'}]]}
-    b = {'z' => [[{'b' => 'b', 'a' => 'a'}]]}
-    assert_equal Job.deep_sort_hash(a).to_json, Job.deep_sort_hash(b).to_json
-  end
-
-  def try_find_reusable
-    foobar = jobs(:foobar)
-    example_attrs = {
-      script_version: foobar.script_version,
-      script: foobar.script,
-      script_parameters: foobar.script_parameters,
-      repository: foobar.repository,
-    }
-
-    # Two matching jobs exist with identical outputs. The older one
-    # should be reused.
-    j = Job.find_reusable(example_attrs, {}, [], [users(:active)])
-    assert j
-    assert_equal foobar.uuid, j.uuid
-
-    # Two matching jobs exist with different outputs. Neither should
-    # be reused.
-    Job.where(uuid: jobs(:job_with_latest_version).uuid).
-      update_all(output: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa+1')
-    assert_nil Job.find_reusable(example_attrs, {}, [], [users(:active)])
-
-    # ...unless config says to reuse the earlier job in such cases.
-    Rails.configuration.Containers.JobsAPI.ReuseJobIfOutputsDiffer = true
-    j = Job.find_reusable(example_attrs, {}, [], [users(:active)])
-    assert_equal foobar.uuid, j.uuid
-  end
-
-  test 'enable legacy api configuration option = true' do
-    Rails.configuration.Containers.JobsAPI.Enable = "true"
-    check_enable_legacy_jobs_api
-    assert_equal(Disable_update_jobs_api_method_list, Rails.configuration.API.DisabledAPIs)
-  end
-
-  test 'enable legacy api configuration option = false' do
-    Rails.configuration.Containers.JobsAPI.Enable = "false"
-    check_enable_legacy_jobs_api
-    assert_equal Disable_jobs_api_method_list, Rails.configuration.API.DisabledAPIs
-  end
-
-  test 'enable legacy api configuration option = auto, has jobs' do
-    Rails.configuration.Containers.JobsAPI.Enable = "auto"
-    assert Job.count > 0
-    check_enable_legacy_jobs_api
-    assert_equal(Disable_update_jobs_api_method_list, Rails.configuration.API.DisabledAPIs)
-  end
-
-  test 'enable legacy api configuration option = auto, no jobs' do
-    Rails.configuration.Containers.JobsAPI.Enable = "auto"
-    act_as_system_user do
-      Job.destroy_all
-    end
-    assert_equal 0, Job.count
-    assert_equal({}, Rails.configuration.API.DisabledAPIs)
-    check_enable_legacy_jobs_api
-    assert_equal Disable_jobs_api_method_list, Rails.configuration.API.DisabledAPIs
-  end
-end
diff --git a/services/api/test/unit/link_test.rb b/services/api/test/unit/link_test.rb
index b9806486ad..fb0d028337 100644
--- a/services/api/test/unit/link_test.rb
+++ b/services/api/test/unit/link_test.rb
@@ -13,7 +13,7 @@ class LinkTest < ActiveSupport::TestCase
 
   test "cannot delete an object referenced by unwritable links" do
     ob = act_as_user users(:active) do
-      Specimen.create
+      Collection.create
     end
     link = act_as_user users(:admin) do
       Link.create(tail_uuid: users(:active).uuid,
diff --git a/services/api/test/unit/log_test.rb b/services/api/test/unit/log_test.rb
index d3a1b618d5..e69f0517e4 100644
--- a/services/api/test/unit/log_test.rb
+++ b/services/api/test/unit/log_test.rb
@@ -32,6 +32,10 @@ class LogTest < ActiveSupport::TestCase
     Log.where(object_uuid: thing.uuid).order("created_at ASC").all
   end
 
+  def clear_logs_about(thing)
+    Log.where(object_uuid: thing.uuid).delete_all
+  end
+
   def assert_logged(thing, event_type)
     logs = get_logs_about(thing)
     assert_equal(@log_count, logs.size, "log count mismatch")
@@ -106,10 +110,11 @@ class LogTest < ActiveSupport::TestCase
 
   test "old_attributes preserves values deep inside a hash" do
     set_user_from_auth :active
-    it = specimens(:owned_by_active_user)
+    it = collections(:collection_owned_by_active)
+    clear_logs_about it
     it.properties = {'foo' => {'bar' => ['baz', 'qux', {'quux' => 'bleat'}]}}
     it.save!
-    @log_count += 1
+    assert_logged it, :update
     it.properties['foo']['bar'][2]['quux'] = 'blert'
     it.save!
     assert_logged it, :update do |props|
@@ -231,6 +236,7 @@ class LogTest < ActiveSupport::TestCase
   test "don't log changes only to Collection.preserve_version" do
     set_user_from_auth :admin_trustedclient
     col = collections(:collection_owned_by_active)
+    clear_logs_about col
     start_log_count = get_logs_about(col).size
     assert_equal false, col.preserve_version
     col.preserve_version = true
@@ -258,27 +264,29 @@ class LogTest < ActiveSupport::TestCase
 
   test "use ownership and permission links to determine which logs a user can see" do
     known_logs = [:noop,
-                  :admin_changes_repository2,
-                  :admin_changes_specimen,
+                  :admin_changes_collection_owned_by_active,
+                  :admin_changes_collection_owned_by_foo,
                   :system_adds_foo_file,
                   :system_adds_baz,
                   :log_owned_by_active,
-                  :crunchstat_for_running_job]
+                  :crunchstat_for_running_container]
 
     c = Log.readable_by(users(:admin)).order("id asc").each.to_a
     assert_log_result c, known_logs, known_logs
 
     c = Log.readable_by(users(:active)).order("id asc").each.to_a
-    assert_log_result c, known_logs, [:admin_changes_repository2, # owned by active
-                                      :system_adds_foo_file,      # readable via link
-                                      :system_adds_baz,           # readable via 'all users' group
-                                      :log_owned_by_active,       # log owned by active
-                                      :crunchstat_for_running_job] # log & job owned by active
+    assert_log_result c, known_logs, [:admin_changes_collection_owned_by_active,
+                                      :system_adds_foo_file,             # readable via link
+                                      :system_adds_baz,                  # readable via 'all users' group
+                                      :log_owned_by_active,              # log owned by active
+                                      :crunchstat_for_running_container] # log & job owned by active
 
     c = Log.readable_by(users(:spectator)).order("id asc").each.to_a
-    assert_log_result c, known_logs, [:noop,                   # object_uuid is spectator
-                                      :admin_changes_specimen, # object_uuid is a specimen owned by spectator
-                                      :system_adds_baz] # readable via 'all users' group
+    assert_log_result c, known_logs, [:noop,                             # object_uuid is spectator
+                                      :system_adds_baz]                  # readable via 'all users' group
+
+    c = Log.readable_by(users(:user_foo_in_sharing_group)).order("id asc").each.to_a
+    assert_log_result c, known_logs, [:admin_changes_collection_owned_by_foo] # collection's parent is readable via role group
   end
 
   def assert_log_result result, known_logs, expected_logs
diff --git a/services/api/test/unit/owner_test.rb b/services/api/test/unit/owner_test.rb
index 1c1bd93b81..a96170c716 100644
--- a/services/api/test/unit/owner_test.rb
+++ b/services/api/test/unit/owner_test.rb
@@ -11,7 +11,7 @@ require 'test_helper'
 # "i" is an item.
 
 class OwnerTest < ActiveSupport::TestCase
-  fixtures :users, :groups, :specimens
+  fixtures :users, :groups
 
   setup do
     set_user_from_auth :admin_trustedclient
@@ -26,22 +26,22 @@ class OwnerTest < ActiveSupport::TestCase
       else
         o = o_class.create!
       end
-      i = Specimen.create(owner_uuid: o.uuid)
+      i = Collection.create(owner_uuid: o.uuid)
       assert i.valid?, "new item should pass validation"
       assert i.uuid, "new item should have an ID"
-      assert Specimen.where(uuid: i.uuid).any?, "new item should really be in DB"
+      assert Collection.where(uuid: i.uuid).any?, "new item should really be in DB"
     end
 
     test "create object with non-existent #{o_class} owner" do
       assert_raises(ActiveRecord::RecordInvalid,
                     "create should fail with random owner_uuid") do
-        Specimen.create!(owner_uuid: o_class.generate_uuid)
+        Collection.create!(owner_uuid: o_class.generate_uuid)
       end
 
-      i = Specimen.create(owner_uuid: o_class.generate_uuid)
+      i = Collection.create(owner_uuid: o_class.generate_uuid)
       assert !i.valid?, "object with random owner_uuid should not be valid?"
 
-      i = Specimen.new(owner_uuid: o_class.generate_uuid)
+      i = Collection.new(owner_uuid: o_class.generate_uuid)
       assert !i.valid?, "new item should not pass validation"
       assert !i.uuid, "new item should not have an ID"
     end
@@ -53,7 +53,7 @@ class OwnerTest < ActiveSupport::TestCase
             else
               o_class.create!
             end
-        i = Specimen.create!(owner_uuid: o.uuid)
+        i = Collection.create!(owner_uuid: o.uuid)
 
         new_o = if new_o_class == Group
               new_o_class.create! group_class: "project"
@@ -61,7 +61,7 @@ class OwnerTest < ActiveSupport::TestCase
               new_o_class.create!
             end
 
-        assert(Specimen.where(uuid: i.uuid).any?,
+        assert(Collection.where(uuid: i.uuid).any?,
                "new item should really be in DB")
         assert(i.update(owner_uuid: new_o.uuid),
                "should change owner_uuid from #{o.uuid} to #{new_o.uuid}")
@@ -102,7 +102,7 @@ class OwnerTest < ActiveSupport::TestCase
   ['users(:active)', 'groups(:aproject)'].each do |ofixt|
     test "delete #{ofixt} that owns other objects" do
       o = eval ofixt
-      assert_equal(true, Specimen.where(owner_uuid: o.uuid).any?,
+      assert_equal(true, Collection.where(owner_uuid: o.uuid).any?,
                    "need something to be owned by #{o.uuid} for this test")
 
       skip_check_permissions_against_full_refresh do
@@ -115,7 +115,7 @@ class OwnerTest < ActiveSupport::TestCase
 
     test "change uuid of #{ofixt} that owns other objects" do
       o = eval ofixt
-      assert_equal(true, Specimen.where(owner_uuid: o.uuid).any?,
+      assert_equal(true, Collection.where(owner_uuid: o.uuid).any?,
                    "need something to be owned by #{o.uuid} for this test")
       new_uuid = o.uuid.sub(/..........$/, rand(2**256).to_s(36)[0..9])
       assert(!o.update(uuid: new_uuid),
diff --git a/services/api/test/unit/permission_test.rb b/services/api/test/unit/permission_test.rb
index 14c810d81a..3059896061 100644
--- a/services/api/test/unit/permission_test.rb
+++ b/services/api/test/unit/permission_test.rb
@@ -222,7 +222,7 @@ class PermissionTest < ActiveSupport::TestCase
     Rails.configuration.Users.ActivatedUsersAreVisibleToOthers = false
     manager = create :active_user, first_name: "Manage", last_name: "Er"
     minion = create :active_user, first_name: "Min", last_name: "Ion"
-    minions_specimen = act_as_user minion do
+    minions_collection = act_as_user minion do
       g = Group.create! name: "minon project", group_class: "project"
       Collection.create! owner_uuid: g.uuid
     end
@@ -289,11 +289,11 @@ class PermissionTest < ActiveSupport::TestCase
       end
       assert_empty(Collection
                      .readable_by(manager)
-                     .where(uuid: minions_specimen.uuid),
+                     .where(uuid: minions_collection.uuid),
                    "manager saw the minion's private stuff")
       assert_raises(ArvadosModel::PermissionDeniedError,
                    "manager could update minion's private stuff") do
-        minions_specimen.update(properties: {'x' => 'y'})
+        minions_collection.update(properties: {'x' => 'y'})
       end
     end
 
@@ -307,11 +307,11 @@ class PermissionTest < ActiveSupport::TestCase
       # Now, manager can read and write Minion's stuff.
       assert_not_empty(Collection
                          .readable_by(manager)
-                         .where(uuid: minions_specimen.uuid),
-                       "manager could not find minion's specimen by uuid")
+                         .where(uuid: minions_collection.uuid),
+                       "manager could not find minion's collection by uuid")
       assert_equal(true,
-                   minions_specimen.update(properties: {'x' => 'y'}),
-                   "manager could not update minion's specimen object")
+                   minions_collection.update(properties: {'x' => 'y'}),
+                   "manager could not update minion's collection object")
     end
   end
 
@@ -341,12 +341,12 @@ class PermissionTest < ActiveSupport::TestCase
     assert_not_empty(User.readable_by(a).where(uuid: b.uuid),
                      "#{a.first_name} should be able to see 'b' in the user list")
 
-    a_specimen = act_as_user a do
+    a_collection = act_as_user a do
       Collection.create!
     end
-    assert_not_empty(Collection.readable_by(a).where(uuid: a_specimen.uuid),
+    assert_not_empty(Collection.readable_by(a).where(uuid: a_collection.uuid),
                      "A cannot read own Collection, following test probably useless.")
-    assert_empty(Collection.readable_by(b).where(uuid: a_specimen.uuid),
+    assert_empty(Collection.readable_by(b).where(uuid: a_collection.uuid),
                  "B can read A's Collection")
     [a,b].each do |u|
       assert_empty(User.readable_by(u).where(uuid: other.uuid),
diff --git a/services/api/test/unit/pipeline_instance_test.rb b/services/api/test/unit/pipeline_instance_test.rb
deleted file mode 100644
index 614c169e85..0000000000
--- a/services/api/test/unit/pipeline_instance_test.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class PipelineInstanceTest < ActiveSupport::TestCase
-
-  [:has_component_with_no_script_parameters,
-   :has_component_with_empty_script_parameters].each do |pi_name|
-    test "update pipeline that #{pi_name}" do
-      pi = pipeline_instances pi_name
-
-      Thread.current[:user] = users(:active)
-      assert_equal PipelineInstance::Ready, pi.state
-    end
-  end
-end
diff --git a/services/api/test/unit/pipeline_template_test.rb b/services/api/test/unit/pipeline_template_test.rb
deleted file mode 100644
index 8ead613b80..0000000000
--- a/services/api/test/unit/pipeline_template_test.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class PipelineTest < ActiveSupport::TestCase
-  # test "the truth" do
-  #   assert true
-  # end
-end
diff --git a/services/api/test/unit/repository_test.rb b/services/api/test/unit/repository_test.rb
deleted file mode 100644
index 674a34ffd8..0000000000
--- a/services/api/test/unit/repository_test.rb
+++ /dev/null
@@ -1,283 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-require 'helpers/git_test_helper'
-
-class RepositoryTest < ActiveSupport::TestCase
-  include GitTestHelper
-
-  def new_repo(owner_key, attrs={})
-    set_user_from_auth owner_key
-    owner = users(owner_key)
-    Repository.new({owner_uuid: owner.uuid}.merge(attrs))
-  end
-
-  def changed_repo(repo_key, changes)
-    repo = repositories(repo_key)
-    changes.each_pair { |attr, value| repo.send("#{attr}=".to_sym, value) }
-    repo
-  end
-
-  def default_git_url(repo_name, user_name=nil)
-    if user_name
-      "git at git.%s.arvadosapi.com:%s/%s.git" %
-        [Rails.configuration.ClusterID, user_name, repo_name]
-    else
-      "git at git.%s.arvadosapi.com:%s.git" %
-        [Rails.configuration.ClusterID, repo_name]
-    end
-  end
-
-  def assert_server_path(path_tail, repo_sym)
-    assert_equal(File.join(Rails.configuration.Git.Repositories, path_tail),
-                 repositories(repo_sym).server_path)
-  end
-
-  ### name validation
-
-  {active: "active/", admin: "admin/", system_user: ""}.
-      each_pair do |user_sym, name_prefix|
-    test "valid names for #{user_sym} repo" do
-      %w(a aa a0 aA Aa AA A0).each do |name|
-        repo = new_repo(user_sym, name: name_prefix + name)
-        assert(repo.valid?)
-      end
-    end
-
-    test "name is required for #{user_sym} repo" do
-      refute(new_repo(user_sym).valid?)
-    end
-
-    test "repo name beginning with numeral is invalid for #{user_sym}" do
-      repo = new_repo(user_sym, name: "#{name_prefix}0a")
-      refute(repo.valid?)
-    end
-
-    test "name containing bad char is invalid for #{user_sym}" do
-      "\\.-_/!@#$%^&*()[]{}".each_char do |bad_char|
-        repo = new_repo(user_sym, name: "#{name_prefix}bad#{bad_char}reponame")
-        refute(repo.valid?)
-      end
-    end
-  end
-
-  test "admin can create valid repo for other user with correct name prefix" do
-    owner = users(:active)
-    repo = new_repo(:admin, name: "#{owner.username}/validnametest",
-                    owner_uuid: owner.uuid)
-    assert(repo.valid?)
-  end
-
-  test "admin can create valid system repo without name prefix" do
-    repo = new_repo(:admin, name: "validnametest",
-                    owner_uuid: users(:system_user).uuid)
-    assert(repo.valid?)
-  end
-
-  test "repo name prefix must match owner_uuid username" do
-    repo = new_repo(:admin, name: "admin/badusernametest",
-                    owner_uuid: users(:active).uuid)
-    refute(repo.valid?)
-  end
-
-  test "repo name prefix must be empty for system repo" do
-    repo = new_repo(:admin, name: "root/badprefixtest",
-                    owner_uuid: users(:system_user).uuid)
-    refute(repo.valid?)
-  end
-
-  ### owner validation
-
-  test "name must be unique per user" do
-    repo = new_repo(:active, name: repositories(:foo).name)
-    refute(repo.valid?)
-  end
-
-  test "name can be duplicated across users" do
-    repo = new_repo(:active, name: "active/#{repositories(:arvados).name}")
-    assert(repo.valid?)
-  end
-
-  test "repository cannot be owned by a group" do
-    set_user_from_auth :active
-    repo = Repository.new(owner_uuid: groups(:all_users).uuid,
-                          name: "ownedbygroup")
-    refute(repo.valid?)
-    refute_empty(repo.errors[:owner_uuid] || [])
-  end
-
-  ### URL generation
-
-  test "fetch_url" do
-    repo = new_repo(:active, name: "active/fetchtest")
-    repo.save
-    assert_equal(default_git_url("fetchtest", "active"), repo.fetch_url)
-  end
-
-  test "fetch_url owned by system user" do
-    set_user_from_auth :admin
-    repo = Repository.new(owner_uuid: users(:system_user).uuid,
-                          name: "fetchtest")
-    repo.save
-    assert_equal(default_git_url("fetchtest"), repo.fetch_url)
-  end
-
-  test "push_url" do
-    repo = new_repo(:active, name: "active/pushtest")
-    repo.save
-    assert_equal(default_git_url("pushtest", "active"), repo.push_url)
-  end
-
-  test "push_url owned by system user" do
-    set_user_from_auth :admin
-    repo = Repository.new(owner_uuid: users(:system_user).uuid,
-                          name: "pushtest")
-    repo.save
-    assert_equal(default_git_url("pushtest"), repo.push_url)
-  end
-
-  ### Path generation
-
-  test "disk path stored by UUID" do
-    assert_server_path("zzzzz-s0uqq-382brsig8rp3666/.git", :foo)
-  end
-
-  test "disk path stored by name" do
-    assert_server_path("arvados/.git", :arvados)
-  end
-
-  test "disk path for repository not on disk" do
-    assert_nil(Repository.new.server_path)
-  end
-
-  ### Repository creation
-
-  test "non-admin can create a repository for themselves" do
-    repo = new_repo(:active, name: "active/newtestrepo")
-    assert(repo.save)
-  end
-
-  test "non-admin can't create a repository for another visible user" do
-    repo = new_repo(:active, name: "repoforanon",
-                    owner_uuid: users(:anonymous).uuid)
-    assert_not_allowed { repo.save }
-  end
-
-  test "admin can create a repository for themselves" do
-    repo = new_repo(:admin, name: "admin/newtestrepo")
-    assert(repo.save)
-  end
-
-  test "admin can create a repository for others" do
-    repo = new_repo(:admin, name: "active/repoforactive",
-                    owner_uuid: users(:active).uuid)
-    assert(repo.save)
-  end
-
-  test "admin can create a system repository" do
-    repo = new_repo(:admin, name: "repoforsystem",
-                    owner_uuid: users(:system_user).uuid)
-    assert(repo.save)
-  end
-
-  ### Repository destruction
-
-  test "non-admin can destroy their own repository" do
-    set_user_from_auth :active
-    assert(repositories(:foo).destroy)
-  end
-
-  test "non-admin can't destroy others' repository" do
-    set_user_from_auth :active
-    assert_not_allowed { repositories(:repository3).destroy }
-  end
-
-  test "non-admin can't destroy system repository" do
-    set_user_from_auth :active
-    assert_not_allowed { repositories(:arvados).destroy }
-  end
-
-  test "admin can destroy their own repository" do
-    set_user_from_auth :admin
-    assert(repositories(:repository3).destroy)
-  end
-
-  test "admin can destroy others' repository" do
-    set_user_from_auth :admin
-    assert(repositories(:foo).destroy)
-  end
-
-  test "admin can destroy system repository" do
-    set_user_from_auth :admin
-    assert(repositories(:arvados).destroy)
-  end
-
-  ### Changing ownership
-
-  test "non-admin can't make their repository a system repository" do
-    set_user_from_auth :active
-    repo = changed_repo(:foo, owner_uuid: users(:system_user).uuid)
-    assert_not_allowed { repo.save }
-  end
-
-  test "admin can give their repository to someone else" do
-    set_user_from_auth :admin
-    repo = changed_repo(:repository3, owner_uuid: users(:active).uuid,
-                        name: "active/foo3")
-    assert(repo.save)
-  end
-
-  test "admin can make their repository a system repository" do
-    set_user_from_auth :admin
-    repo = changed_repo(:repository3, owner_uuid: users(:system_user).uuid,
-                        name: "foo3")
-    assert(repo.save)
-  end
-
-  test 'write permission allows changing modified_at' do
-    act_as_user users(:active) do
-      r = repositories(:foo)
-      modtime_was = r.modified_at
-      r.modified_at = Time.now
-      assert r.save
-      assert_operator modtime_was, :<, r.modified_at
-    end
-  end
-
-  test 'write permission necessary for changing modified_at' do
-    act_as_user users(:spectator) do
-      r = repositories(:foo)
-      modtime_was = r.modified_at
-      r.modified_at = Time.now
-      assert_raises ArvadosModel::PermissionDeniedError do
-        r.save!
-      end
-      r.reload
-      assert_equal modtime_was, r.modified_at
-    end
-  end
-
-  ### Renaming
-
-  test "non-admin can rename own repo" do
-    act_as_user users(:active) do
-      assert repositories(:foo).update(name: 'active/foo12345')
-    end
-  end
-
-  test "top level repo can be touched by non-admin with can_manage" do
-    add_permission_link users(:active), repositories(:arvados), 'can_manage'
-    act_as_user users(:active) do
-      assert changed_repo(:arvados, modified_at: Time.now).save
-    end
-  end
-
-  test "top level repo cannot be renamed by non-admin with can_manage" do
-    add_permission_link users(:active), repositories(:arvados), 'can_manage'
-    act_as_user users(:active) do
-      assert_not_allowed { changed_repo(:arvados, name: 'xarvados').save }
-    end
-  end
-end
diff --git a/services/api/test/unit/specimen_test.rb b/services/api/test/unit/specimen_test.rb
deleted file mode 100644
index 5b2eda2f0f..0000000000
--- a/services/api/test/unit/specimen_test.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class SpecimenTest < ActiveSupport::TestCase
-  # test "the truth" do
-  #   assert true
-  # end
-end
diff --git a/services/api/test/unit/trait_test.rb b/services/api/test/unit/trait_test.rb
deleted file mode 100644
index fe63f161f8..0000000000
--- a/services/api/test/unit/trait_test.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class TraitTest < ActiveSupport::TestCase
-  # test "the truth" do
-  #   assert true
-  # end
-end
diff --git a/services/api/test/unit/user_test.rb b/services/api/test/unit/user_test.rb
index 810e5b45ec..71b5769be8 100644
--- a/services/api/test/unit/user_test.rb
+++ b/services/api/test/unit/user_test.rb
@@ -118,18 +118,7 @@ class UserTest < ActiveSupport::TestCase
     check_new_username_setting("_", nil)
   end
 
-  test "updating username updates repository names" do
-    set_user_from_auth :admin
-    user = users(:active)
-    user.username = "newtestname"
-    assert(user.save, "username update failed")
-    {foo: "newtestname/foo", repository2: "newtestname/foo2"}.
-        each_pair do |repo_sym, expect_name|
-      assert_equal(expect_name, repositories(repo_sym).name)
-    end
-  end
-
-  test "admin can clear username when user owns no repositories" do
+  test "admin can clear username" do
     set_user_from_auth :admin
     user = users(:spectator)
     user.username = nil
@@ -137,22 +126,6 @@ class UserTest < ActiveSupport::TestCase
     assert_nil(user.username)
   end
 
-  test "admin can't clear username when user owns repositories" do
-    set_user_from_auth :admin
-    user = users(:active)
-    user.username = nil
-    assert_not_allowed { user.save }
-    refute_empty(user.errors[:username])
-  end
-
-  test "failed username update doesn't change repository names" do
-    set_user_from_auth :admin
-    user = users(:active)
-    user.username = users(:fuse).username
-    assert_not_allowed { user.save }
-    assert_equal("active/foo", repositories(:foo).name)
-  end
-
   [[false, 'foo at example.com', true, false],
    [false, 'bar at example.com', false, true],
    [true, 'foo at example.com', true, false],
@@ -359,37 +332,33 @@ class UserTest < ActiveSupport::TestCase
 
   [
     # Easy inactive user tests.
-    [false, empty_notify_list, empty_notify_list, "inactive-none at example.com", false, false, "inactivenone"],
-    [false, empty_notify_list, empty_notify_list, "inactive-vm at example.com", true, false, "inactivevm"],
-    [false, empty_notify_list, empty_notify_list, "inactive-repo at example.com", false, true, "inactiverepo"],
-    [false, empty_notify_list, empty_notify_list, "inactive-both at example.com", true, true, "inactiveboth"],
+    [false, empty_notify_list, empty_notify_list, "inactive-none at example.com", false, "inactivenone"],
+    [false, empty_notify_list, empty_notify_list, "inactive-vm at example.com", true, "inactivevm"],
 
     # Easy active user tests.
-    [true, active_notify_list, inactive_notify_list, "active-none at example.com", false, false, "activenone"],
-    [true, active_notify_list, inactive_notify_list, "active-vm at example.com", true, false, "activevm"],
-    [true, active_notify_list, inactive_notify_list, "active-repo at example.com", false, true, "activerepo"],
-    [true, active_notify_list, inactive_notify_list, "active-both at example.com", true, true, "activeboth"],
+    [true, active_notify_list, inactive_notify_list, "active-none at example.com", false, "activenone"],
+    [true, active_notify_list, inactive_notify_list, "active-vm at example.com", true, "activevm"],
 
     # Test users with malformed e-mail addresses.
-    [false, empty_notify_list, empty_notify_list, nil, true, true, nil],
-    [false, empty_notify_list, empty_notify_list, "arvados", true, true, nil],
-    [false, empty_notify_list, empty_notify_list, "@example.com", true, true, nil],
-    [true, active_notify_list, inactive_notify_list, "*!*@example.com", true, false, nil],
-    [true, active_notify_list, inactive_notify_list, "*!*@example.com", false, false, nil],
+    [false, empty_notify_list, empty_notify_list, nil, true, nil],
+    [false, empty_notify_list, empty_notify_list, "arvados", true, nil],
+    [false, empty_notify_list, empty_notify_list, "@example.com", true, nil],
+    [true, active_notify_list, inactive_notify_list, "*!*@example.com", true, nil],
+    [true, active_notify_list, inactive_notify_list, "*!*@example.com", false, nil],
 
     # Test users with various username transformations.
-    [false, empty_notify_list, empty_notify_list, "arvados at example.com", false, false, "arvados2"],
-    [true, active_notify_list, inactive_notify_list, "arvados at example.com", false, false, "arvados2"],
-    [true, active_notify_list, inactive_notify_list, "root at example.com", true, false, "root2"],
-    [false, active_notify_list, empty_notify_list, "root at example.com", true, false, "root2"],
-    [true, active_notify_list, inactive_notify_list, "roo_t at example.com", false, true, "root2"],
-    [false, empty_notify_list, empty_notify_list, "^^incorrect_format at example.com", true, true, "incorrectformat"],
-    [true, active_notify_list, inactive_notify_list, "&4a_d9. at example.com", true, true, "ad9"],
-    [true, active_notify_list, inactive_notify_list, "&4a_d9. at example.com", false, false, "ad9"],
-    [false, active_notify_list, empty_notify_list, "&4a_d9. at example.com", true, true, "ad9"],
-    [false, active_notify_list, empty_notify_list, "&4a_d9. at example.com", false, false, "ad9"],
-  ].each do |active, new_user_recipients, inactive_recipients, email, auto_setup_vm, auto_setup_repo, expect_username|
-    test "create new user with auto setup active=#{active} email=#{email} vm=#{auto_setup_vm} repo=#{auto_setup_repo}" do
+    [false, empty_notify_list, empty_notify_list, "arvados at example.com", false, "arvados2"],
+    [true, active_notify_list, inactive_notify_list, "arvados at example.com", false, "arvados2"],
+    [true, active_notify_list, inactive_notify_list, "root at example.com", true, "root2"],
+    [false, active_notify_list, empty_notify_list, "root at example.com", true, "root2"],
+    [true, active_notify_list, inactive_notify_list, "roo_t at example.com", false, "root2"],
+    [false, empty_notify_list, empty_notify_list, "^^incorrect_format at example.com", true, "incorrectformat"],
+    [true, active_notify_list, inactive_notify_list, "&4a_d9. at example.com", true, "ad9"],
+    [true, active_notify_list, inactive_notify_list, "&4a_d9. at example.com", false, "ad9"],
+    [false, active_notify_list, empty_notify_list, "&4a_d9. at example.com", true, "ad9"],
+    [false, active_notify_list, empty_notify_list, "&4a_d9. at example.com", false, "ad9"],
+  ].each do |active, new_user_recipients, inactive_recipients, email, auto_setup_vm, expect_username|
+    test "create new user with auto setup active=#{active} email=#{email} vm=#{auto_setup_vm}" do
       set_user_from_auth :admin
 
       Rails.configuration.Users.AutoSetupNewUsers = true
@@ -400,8 +369,6 @@ class UserTest < ActiveSupport::TestCase
         Rails.configuration.Users.AutoSetupNewUsersWithVmUUID = ""
       end
 
-      Rails.configuration.Users.AutoSetupNewUsersWithRepository = auto_setup_repo
-
       create_user_and_verify_setup_and_notifications active, new_user_recipients, inactive_recipients, email, expect_username
     end
   end
@@ -460,8 +427,7 @@ class UserTest < ActiveSupport::TestCase
 
       vm = VirtualMachine.create
 
-      response = user.setup(repo_name: 'foo/testrepo',
-                            vm_uuid: vm.uuid)
+      response = user.setup(vm_uuid: vm.uuid)
 
       resp_user = find_obj_in_resp response, 'User'
       verify_user resp_user, email
@@ -476,9 +442,6 @@ class UserTest < ActiveSupport::TestCase
         assert_nil group_perm2
       end
 
-      repo_perm = find_obj_in_resp response, 'Link', 'arvados#repository'
-      verify_link repo_perm, 'permission', 'can_manage', resp_user[:uuid], nil
-
       vm_perm = find_obj_in_resp response, 'Link', 'arvados#virtualMachine'
       verify_link vm_perm, 'permission', 'can_login', resp_user[:uuid], vm.uuid
       assert_equal("foo", vm_perm.properties["username"])
@@ -494,8 +457,7 @@ class UserTest < ActiveSupport::TestCase
 
     vm = VirtualMachine.create
 
-    response = user.setup(repo_name: 'foo/testrepo',
-                          vm_uuid: vm.uuid)
+    response = user.setup(vm_uuid: vm.uuid)
 
     resp_user = find_obj_in_resp response, 'User'
     verify_user resp_user, email
@@ -503,9 +465,6 @@ class UserTest < ActiveSupport::TestCase
     group_perm = find_obj_in_resp response, 'Link', 'arvados#group'
     verify_link group_perm, 'permission', 'can_write', resp_user[:uuid], groups(:all_users).uuid
 
-    repo_perm = find_obj_in_resp response, 'Link', 'arvados#repository'
-    verify_link repo_perm, 'permission', 'can_manage', resp_user[:uuid], nil
-
     vm_perm = find_obj_in_resp response, 'Link', 'arvados#virtualMachine'
     verify_link vm_perm, 'permission', 'can_login', resp_user[:uuid], vm.uuid
     assert_equal("foo", vm_perm.properties["username"])
@@ -529,23 +488,10 @@ class UserTest < ActiveSupport::TestCase
     group_perm2 = find_obj_in_resp response, 'Link', 'arvados#user'
     verify_link group_perm2, 'permission', 'can_read', groups(:all_users).uuid, nil
 
-    # invoke setup again with repo_name
-    response = user.setup(repo_name: 'foo/testrepo')
-    resp_user = find_obj_in_resp response, 'User', nil
-    verify_user resp_user, email
-    assert_equal user.uuid, resp_user[:uuid], 'expected uuid not found'
-
-    group_perm = find_obj_in_resp response, 'Link', 'arvados#group'
-    verify_link group_perm, 'permission', 'can_write', resp_user[:uuid], groups(:all_users).uuid
-
-    repo_perm = find_obj_in_resp response, 'Link', 'arvados#repository'
-    verify_link repo_perm, 'permission', 'can_manage', resp_user[:uuid], nil
-
     # invoke setup again with a vm_uuid
     vm = VirtualMachine.create
 
-    response = user.setup(repo_name: 'foo/testrepo',
-                          vm_uuid: vm.uuid)
+    response = user.setup(vm_uuid: vm.uuid)
 
     resp_user = find_obj_in_resp response, 'User', nil
     verify_user resp_user, email
@@ -554,9 +500,6 @@ class UserTest < ActiveSupport::TestCase
     group_perm = find_obj_in_resp response, 'Link', 'arvados#group'
     verify_link group_perm, 'permission', 'can_write', resp_user[:uuid], groups(:all_users).uuid
 
-    repo_perm = find_obj_in_resp response, 'Link', 'arvados#repository'
-    verify_link repo_perm, 'permission', 'can_manage', resp_user[:uuid], nil
-
     vm_perm = find_obj_in_resp response, 'Link', 'arvados#virtualMachine'
     verify_link vm_perm, 'permission', 'can_login', resp_user[:uuid], vm.uuid
     assert_equal("foo", vm_perm.properties["username"])
@@ -614,8 +557,6 @@ class UserTest < ActiveSupport::TestCase
 
     can_setup = (Rails.configuration.Users.AutoSetupNewUsers and
                  (not expect_username.nil?))
-    expect_repo_name = "#{expect_username}/#{expect_username}"
-    prior_repo = Repository.where(name: expect_repo_name).first
 
     user = User.new
     user.first_name = "first_name_for_newly_created_user"
@@ -629,14 +570,6 @@ class UserTest < ActiveSupport::TestCase
                        groups(:all_users).uuid, user.uuid,
                        "permission", "can_write")
 
-    # Check for repository.
-    if named_repo = (prior_repo or
-                     Repository.where(name: expect_repo_name).first)
-      verify_link_exists((can_setup and prior_repo.nil? and
-                          Rails.configuration.Users.AutoSetupNewUsersWithRepository),
-                         named_repo.uuid, user.uuid, "permission", "can_manage")
-    end
-
     # Check for VM login.
     if (auto_vm_uuid = Rails.configuration.Users.AutoSetupNewUsersWithVmUUID) != ""
       verify_link_exists(can_setup, auto_vm_uuid, user.uuid,
diff --git a/services/ws/event_source_test.go b/services/ws/event_source_test.go
index d02b199939..172f74ce7a 100644
--- a/services/ws/event_source_test.go
+++ b/services/ws/event_source_test.go
@@ -7,11 +7,14 @@ package ws
 import (
 	"database/sql"
 	"fmt"
+	"io/ioutil"
+	"sort"
 	"sync"
 	"time"
 
 	"git.arvados.org/arvados.git/sdk/go/arvados"
 	"git.arvados.org/arvados.git/sdk/go/ctxlog"
+	"github.com/ghodss/yaml"
 	"github.com/prometheus/client_golang/prometheus"
 	check "gopkg.in/check.v1"
 )
@@ -41,6 +44,18 @@ func testDB() *sql.DB {
 }
 
 func (*eventSourceSuite) TestEventSource(c *check.C) {
+	var logfixtures map[string]struct {
+		ID int
+	}
+	yamldata, err := ioutil.ReadFile("../api/test/fixtures/logs.yml")
+	c.Assert(err, check.IsNil)
+	c.Assert(yaml.Unmarshal(yamldata, &logfixtures), check.IsNil)
+	var logIDs []int
+	for _, logfixture := range logfixtures {
+		logIDs = append(logIDs, logfixture.ID)
+	}
+	sort.Ints(logIDs)
+
 	cfg := testDBConfig()
 	db := testDB()
 	pges := &pgEventSource{
@@ -61,8 +76,8 @@ func (*eventSourceSuite) TestEventSource(c *check.C) {
 	done := make(chan bool, 1)
 
 	go func() {
-		for i := range sinks {
-			_, err := db.Exec(fmt.Sprintf(`NOTIFY logs, '%d'`, i))
+		for _, id := range logIDs {
+			_, err := db.Exec(fmt.Sprintf(`NOTIFY logs, '%d'`, id))
 			if err != nil {
 				done <- true
 				c.Fatal(err)
@@ -77,17 +92,13 @@ func (*eventSourceSuite) TestEventSource(c *check.C) {
 		go func(si int, s eventSink) {
 			defer wg.Done()
 			defer sinks[si].Stop()
-			for i := 0; i <= si; i++ {
+			for _, logID := range logIDs {
 				ev := <-sinks[si].Channel()
-				c.Logf("sink %d received event %d", si, i)
-				c.Check(ev.LogID, check.Equals, int64(i))
+				c.Logf("sink %d received event %d", si, logID)
+				c.Check(ev.LogID, check.Equals, int64(logID))
 				row := ev.Detail()
-				if i == 0 {
-					// no matching row, null event
-					c.Check(row, check.IsNil)
-				} else {
-					c.Check(row, check.NotNil)
-					c.Check(row.ID, check.Equals, int64(i))
+				if c.Check(row, check.NotNil) {
+					c.Check(row.ID, check.Equals, int64(logID))
 					c.Check(row.UUID, check.Not(check.Equals), "")
 				}
 			}
diff --git a/services/ws/event_test.go b/services/ws/event_test.go
index 4665dfcd9e..f38bbbe5ad 100644
--- a/services/ws/event_test.go
+++ b/services/ws/event_test.go
@@ -21,5 +21,5 @@ func (*eventSuite) TestDetail(c *check.C) {
 	c.Check(logRow.UUID, check.Equals, "zzzzz-57u5n-containerlog006")
 	c.Check(logRow.ObjectUUID, check.Equals, "zzzzz-dz642-logscontainer03")
 	c.Check(logRow.EventType, check.Equals, "crunchstat")
-	c.Check(logRow.Properties["text"], check.Equals, "2013-11-07_23:33:41 zzzzz-8i9sb-ahd7cie8jah9qui 29610 1 stderr crunchstat: cpu 1935.4300 user 59.4100 sys 8 cpus -- interval 10.0002 seconds 12.9900 user 0.9900 sys")
+	c.Check(logRow.Properties["text"], check.Equals, "2013-11-07_23:33:41 zzzzz-dz642-logscontainer03 29610 1 stderr crunchstat: cpu 1935.4300 user 59.4100 sys 8 cpus -- interval 10.0002 seconds 12.9900 user 0.9900 sys")
 }

commit 4e6bd678244252daf46e21edaf22077dbbd55159
Author: Tom Clegg <tom at curii.com>
Date:   Mon Apr 22 14:20:14 2024 -0400

    15397: Silence nginx log noise like "closed keepalive connection".
    
    Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom at curii.com>

diff --git a/lib/boot/nginx.go b/lib/boot/nginx.go
index 9f1091eac3..22f1e665fa 100644
--- a/lib/boot/nginx.go
+++ b/lib/boot/nginx.go
@@ -135,7 +135,7 @@ func (runNginx) Run(ctx context.Context, fail func(error), super *Supervisor) er
 		}
 	}
 
-	configs := "error_log stderr info; "
+	configs := "error_log stderr warn; "
 	configs += "pid " + filepath.Join(super.wwwtempdir, "nginx.pid") + "; "
 	configs += "user www-data; "
 
diff --git a/sdk/python/tests/run_test_server.py b/sdk/python/tests/run_test_server.py
index 787837b723..84b32c2513 100644
--- a/sdk/python/tests/run_test_server.py
+++ b/sdk/python/tests/run_test_server.py
@@ -685,7 +685,7 @@ def run_nginx():
 
     nginx = subprocess.Popen(
         ['nginx',
-         '-g', 'error_log stderr info; pid '+_pidfile('nginx')+';',
+         '-g', 'error_log stderr notice; pid '+_pidfile('nginx')+';',
          '-c', conffile],
         env=env, stdin=open('/dev/null'), stdout=sys.stderr)
     _detachedSubprocesses.append(nginx)

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list