Merge branch 'master' into 7478-anm-spot-instances
authorLucas Di Pentima <ldipentima@veritasgenetics.com>
Fri, 25 May 2018 15:42:25 +0000 (12:42 -0300)
committerLucas Di Pentima <ldipentima@veritasgenetics.com>
Fri, 25 May 2018 15:42:25 +0000 (12:42 -0300)
Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <ldipentima@veritasgenetics.com>

116 files changed:
.licenseignore
AUTHORS
apps/workbench/app/controllers/users_controller.rb
apps/workbench/app/models/user.rb
apps/workbench/app/views/layouts/body.html.erb
apps/workbench/app/views/tests/mithril.html
apps/workbench/app/views/users/inactive.html.erb
apps/workbench/app/views/users/link_account.html.erb [new file with mode: 0644]
apps/workbench/config/routes.rb
apps/workbench/test/integration/link_account_test.rb [new file with mode: 0644]
build/build.list
build/check-copyright-notices
build/run-build-packages.sh
doc/_config.yml
doc/admin/migrating-providers.html.textile.liquid [new file with mode: 0644]
doc/css/images.css
doc/user/topics/link-accounts.html.textile.liquid [new file with mode: 0644]
sdk/R/R/Arvados.R
sdk/R/R/ArvadosFile.R
sdk/R/R/Collection.R
sdk/R/R/CollectionTree.R
sdk/R/R/HttpParser.R
sdk/R/R/HttpRequest.R
sdk/R/R/RESTService.R
sdk/R/R/Subcollection.R
sdk/R/R/autoGenAPI.R
sdk/R/R/util.R
sdk/R/README.Rmd
sdk/R/createDoc.R
sdk/R/install_deps.R
sdk/R/run_test.R
sdk/R/tests/testthat.R
sdk/R/tests/testthat/fakes/FakeArvados.R
sdk/R/tests/testthat/fakes/FakeHttpParser.R
sdk/R/tests/testthat/fakes/FakeHttpRequest.R
sdk/R/tests/testthat/fakes/FakeRESTService.R
sdk/R/tests/testthat/test-ArvadosFile.R
sdk/R/tests/testthat/test-Collection.R
sdk/R/tests/testthat/test-CollectionTree.R
sdk/R/tests/testthat/test-HttpParser.R
sdk/R/tests/testthat/test-HttpRequest.R
sdk/R/tests/testthat/test-RESTService.R
sdk/R/tests/testthat/test-Subcollection.R
sdk/R/tests/testthat/test-util.R
sdk/cwl/arvados_cwl/arvtool.py
sdk/cwl/setup.py
sdk/cwl/tests/12213-keepref-expr.cwl
sdk/cwl/tests/12213-keepref-job.yml
sdk/cwl/tests/12213-keepref-tool.cwl
sdk/cwl/tests/12213-keepref-wf.cwl
sdk/cwl/tests/12418-glob-empty-collection.cwl
sdk/cwl/tests/secondary/ls.cwl
sdk/cwl/tests/secondary/sub.cwl
sdk/cwl/tests/secondary/wf-job.yml
sdk/cwl/tests/secondary/wf.cwl
sdk/cwl/tests/secondaryFiles/example1.cwl
sdk/cwl/tests/secondaryFiles/example3.cwl
sdk/cwl/tests/secondaryFiles/inp3.yml
sdk/cwl/tests/secret_test_job.yml
sdk/cwl/tests/test_job.py
sdk/cwl/tests/wf-defaults/default-dir1.cwl
sdk/cwl/tests/wf-defaults/default-dir2.cwl
sdk/cwl/tests/wf-defaults/default-dir3.cwl
sdk/cwl/tests/wf-defaults/default-dir4.cwl
sdk/cwl/tests/wf-defaults/default-dir5.cwl
sdk/cwl/tests/wf-defaults/default-dir6.cwl
sdk/cwl/tests/wf-defaults/default-dir6a.cwl
sdk/cwl/tests/wf-defaults/default-dir7.cwl
sdk/cwl/tests/wf-defaults/default-dir7a.cwl
sdk/cwl/tests/wf-defaults/wf1.cwl
sdk/cwl/tests/wf-defaults/wf2.cwl
sdk/cwl/tests/wf-defaults/wf3.cwl
sdk/cwl/tests/wf-defaults/wf4.cwl
sdk/cwl/tests/wf-defaults/wf5.cwl
sdk/cwl/tests/wf-defaults/wf6.cwl
sdk/cwl/tests/wf-defaults/wf7.cwl
sdk/cwl/tests/wf/check_mem.py
sdk/cwl/tests/wf/echo-subwf.cwl
sdk/cwl/tests/wf/echo-wf.cwl
sdk/cwl/tests/wf/echo_a.cwl
sdk/cwl/tests/wf/echo_b.cwl
sdk/cwl/tests/wf/secret_job.cwl
sdk/cwl/tests/wf/secret_wf.cwl
sdk/go/arvados/fs_filehandle.go
sdk/go/health/aggregator.go
sdk/go/health/aggregator_test.go
sdk/python/arvados/collection.py
sdk/python/arvados/commands/put.py
sdk/python/tests/test_arv_put.py
sdk/python/tests/test_collections.py
services/api/app/controllers/arvados/v1/containers_controller.rb
services/api/app/controllers/user_sessions_controller.rb
services/api/app/models/container.rb
services/api/app/models/user.rb
services/api/db/migrate/20170704160233_yaml_to_json.rb
services/api/db/migrate/20170706141334_json_collection_properties.rb
services/api/db/migrate/20171027183824_add_index_to_containers.rb
services/api/db/migrate/20171208203841_fix_trash_flag_follow.rb
services/api/db/migrate/20171212153352_add_gin_index_to_collection_properties.rb
services/api/db/migrate/20180228220311_add_secret_mounts_to_containers.rb
services/api/db/migrate/20180313180114_change_container_priority_bigint.rb
services/api/db/migrate/20180501182859_add_redirect_to_user_uuid_to_users.rb
services/api/db/migrate/20180514135529_add_container_auth_uuid_index.rb
services/api/test/fixtures/api_client_authorizations.yml
services/api/test/fixtures/users.yml
services/api/test/integration/user_sessions_test.rb
services/crunch-run/git_mount.go
services/health/main.go
services/keep-balance/main.go
services/keep-balance/usage.go
services/keep-web/webdav_test.go
tools/arvbox/lib/arvbox/docker/runit/1
tools/arvbox/lib/arvbox/docker/runit/2
tools/arvbox/lib/arvbox/docker/runit/3
tools/arvbox/lib/arvbox/docker/runit/ctrlaltdel
tools/crunchstat-summary/crunchstat_summary/synchronizer.js

index 51980b16c2ffcbdd2ab93729c4677048b5160c39..51a1e7cbd2f0aabca972527475630923f1f1ef75 100644 (file)
@@ -26,6 +26,8 @@ docker/jobs/apt.arvados.org.list
 *.gz.report
 *.ico
 *.jpg
+*.svg
+*.odg
 *.json
 *LICENSE*.html
 .licenseignore
@@ -59,3 +61,11 @@ services/arv-web/sample-cgi-app/public/index.cgi
 services/keepproxy/pkg-extras/etc/default/keepproxy
 *.tar
 tools/crunchstat-summary/tests/crunchstat_error_messages.txt
+tools/crunchstat-summary/crunchstat_summary/synchronizer.js
+build/package-build-dockerfiles/debian9/D39DC0E3.asc
+build/package-test-dockerfiles/debian9/D39DC0E3.asc
+sdk/R/DESCRIPTION
+sdk/R/NAMESPACE
+sdk/R/.Rbuildignore
+sdk/R/ArvadosR.Rproj
+*.Rd
diff --git a/AUTHORS b/AUTHORS
index ea9fa4c7092e8c2069a2105d8eafb25e6107d3ab..9a861a6315099a8faec86b854d8737078adc22b7 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -17,3 +17,4 @@ Joshua Randall <joshua.randall@sanger.ac.uk>
 President and Fellows of Harvard College <*@harvard.edu>
 Thomas Mooney <tmooney@genome.wustl.edu>
 Chen Chen <aflyhorse@gmail.com>
+Veritas Genetics, Inc. <*@veritasgenetics.com>
index 2e3ced69a534485ca5d18df22b19ac53abeea793..8cfc2c10f1c29eee67a11074acfabe70da19aaba 100644 (file)
@@ -4,8 +4,8 @@
 
 class UsersController < ApplicationController
   skip_around_filter :require_thread_api_token, only: :welcome
-  skip_before_filter :check_user_agreements, only: [:welcome, :inactive]
-  skip_before_filter :check_user_profile, only: [:welcome, :inactive, :profile]
+  skip_before_filter :check_user_agreements, only: [:welcome, :inactive, :link_account, :merge]
+  skip_before_filter :check_user_profile, only: [:welcome, :inactive, :profile, :link_account, :merge]
   skip_before_filter :find_object_by_uuid, only: [:welcome, :activity, :storage]
   before_filter :ensure_current_user_is_admin, only: [:sudo, :unsetup, :setup]
 
@@ -317,6 +317,11 @@ class UsersController < ApplicationController
     RequestShellAccessReporter.send_request(current_user, params).deliver
   end
 
+  def merge
+    User.merge params[:new_user_token], params[:direction]
+    redirect_to "/"
+  end
+
   protected
 
   def find_current_links user
index 1f102dbf17acd3fb807110c34f4937686ebb9f2d..865ff6e9519cacf613b248df446fd4a1e0b24636 100644 (file)
@@ -14,6 +14,47 @@ class User < ArvadosBase
     arvados_api_client.unpack_api_response(res)
   end
 
+  def self.merge new_user_token, direction
+    # Merge user accounts.
+    #
+    # If the direction is "in", the current user is merged into the
+    # user represented by new_user_token
+    #
+    # If the direction is "out", the user represented by new_user_token
+    # is merged into the current user.
+
+    if direction == "in"
+      user_a = new_user_token
+      user_b = Thread.current[:arvados_api_token]
+      new_group_name = "Migrated from #{Thread.current[:user].email} (#{Thread.current[:user].uuid})"
+    elsif direction == "out"
+      user_a = Thread.current[:arvados_api_token]
+      user_b = new_user_token
+      res = arvados_api_client.api self, '/current', nil, {:arvados_api_token => user_b}, false
+      user_b_info = arvados_api_client.unpack_api_response(res)
+      new_group_name = "Migrated from #{user_b_info.email} (#{user_b_info.uuid})"
+    else
+      raise "Invalid merge direction, expected 'in' or 'out'"
+    end
+
+    # Create a project owned by user_a to accept everything owned by user_b
+    res = arvados_api_client.api Group, nil, {:group => {
+                                                :name => new_group_name,
+                                                :group_class => "project"},
+                                              :ensure_unique_name => true},
+                                 {:arvados_api_token => user_a}, false
+    target = arvados_api_client.unpack_api_response(res)
+
+    # The merge API merges the "current" user (user_b) into the user
+    # represented by "new_user_token" (user_a).
+    # After merging, the user_b redirects to user_a.
+    res = arvados_api_client.api self, '/merge', {:new_user_token => user_a,
+                                                  :new_owner_uuid => target[:uuid],
+                                                  :redirect_to_new_user => true},
+                                 {:arvados_api_token => user_b}, false
+    arvados_api_client.unpack_api_response(res)
+  end
+
   def self.system
     @@arvados_system_user ||= begin
                                 res = arvados_api_client.api self, '/system'
index f4be7cad63ab282f2332235e89cd848d6bfb1771..124a78577f3e5cac875569c8912217d65b8fc1ce 100644 (file)
@@ -93,7 +93,8 @@ SPDX-License-Identifier: AGPL-3.0 %>
                   <%= link_to ssh_keys_user_path(current_user), role: 'menu-item' do %>
                     <i class="fa fa-lg fa-key fa-fw"></i> SSH keys
                   <% end %>
-                </li>
+</li>
+                <li role="menuitem"><a href="/users/link_account" role="menuitem"><i class="fa fa-lg fa-link fa-fw"></i> Link account </a></li>
                 <% if Rails.configuration.user_profile_form_fields %>
                   <li role="menuitem"><a href="/users/<%=current_user.uuid%>/profile" role="menuitem"><i class="fa fa-lg fa-user fa-fw"></i> Manage profile</a></li>
                 <% end %>
index a629eb75fda0a8140e00d411543528e05765d4ae..fac2d88c50586e844754a2016bfd9d04dfde72b5 100644 (file)
@@ -1 +1,5 @@
+<!-- Copyright (C) The Arvados Authors. All rights reserved.
+
+SPDX-License-Identifier: AGPL-3.0 -->
+
 <div data-mount-mithril="TestComponent"></div>
index 389044f92fc2a3ab19337ae95a0f3815950c4314..f3cb3cf5cae7d25bde0ed590a0f81c45a37447f3 100644 (file)
@@ -25,6 +25,11 @@ SPDX-License-Identifier: AGPL-3.0 %>
         <%= link_to 'Retry', (params[:return_to] || '/'), class: 'btn btn-primary' %>
 
       </p>
+
+      <p>
+       Already have an account with a different login?  <a href="/users/link_account">Link this login to your existing account.</a>
+      </p>
+
     </div>
   </div>
 </div>
diff --git a/apps/workbench/app/views/users/link_account.html.erb b/apps/workbench/app/views/users/link_account.html.erb
new file mode 100644 (file)
index 0000000..86a0446
--- /dev/null
@@ -0,0 +1,112 @@
+<%# Copyright (C) The Arvados Authors. All rights reserved.
+
+SPDX-License-Identifier: AGPL-3.0 %>
+
+<%= javascript_tag do %>
+  function update_visibility() {
+    if (sessionStorage.getItem('link_account_api_token') &&
+      sessionStorage.getItem('link_account_uuid') != '<%= Thread.current[:user].uuid %>')
+    {
+      $("#ready-to-link").css({"display": "inherit"});
+      $("#need-login").css({"display": "none"});
+
+      <% if params[:direction] == "in" %>
+      var user_a = "<b>"+sessionStorage.getItem('link_account_email')+"</b> ("+sessionStorage.getItem('link_account_username')+", "+sessionStorage.getItem('link_account_uuid')+")";
+      var user_b = "<b><%= Thread.current[:user].email %></b> (<%= Thread.current[:user].username%>, <%= Thread.current[:user].uuid%>)";
+      var user_a_is_active = (sessionStorage.getItem('link_account_is_active') == "true");
+      var user_a_is_admin = (sessionStorage.getItem('link_account_is_admin') == "true");
+      var user_b_is_admin = <%=if Thread.current[:user].is_admin then "true" else "false" end %>;
+      <% else %>
+      var user_a = "<b><%= Thread.current[:user].email %></b> (<%= Thread.current[:user].username%>, <%= Thread.current[:user].uuid%>)";
+      var user_b = "<b>"+sessionStorage.getItem('link_account_email')+"</b> ("+sessionStorage.getItem('link_account_username')+", "+sessionStorage.getItem('link_account_uuid')+")";
+      var user_a_is_active = <%= Thread.current[:user].is_active %>;
+      var user_a_is_admin = <%=if Thread.current[:user].is_admin then "true" else "false" end %>;
+      var user_b_is_admin = (sessionStorage.getItem('link_account_is_admin') == "true");
+      <% end %>
+
+      $("#new-user-token-input").val(sessionStorage.getItem('link_account_api_token'));
+
+      if (!user_a_is_active) {
+        $("#will-link-to").html("<p>Cannot link "+user_b+" to inactive account "+user_a+".</p>");
+        $("#link-account-submit").prop("disabled", true);
+      } else if (user_b_is_admin && !user_a_is_admin) {
+        $("#will-link-to").html("<p>Cannot link admin account "+user_b+" to non-admin account "+user_a+".</p>");
+        $("#link-account-submit").prop("disabled", true);
+      } else {
+        $("#will-link-to").html("<p>Clicking 'Link accounts' will link "+user_b+" created on <%=Thread.current[:user].created_at%> to "+
+          user_a+" created at <b>"+sessionStorage.getItem('link_account_created_at')+"</b>.</p>"+
+          "<p>After linking, logging in as "+user_b+" will log you into the same account as "+user_a+
+          ".</p>  <p>Any objects owned by "+user_b+" will be transferred to "+user_a+".</p>");
+      }
+    } else {
+      $("#ready-to-link").css({"display": "none"});
+      $("#need-login").css({"display": "inherit"});
+    }
+
+    sessionStorage.removeItem('link_account_api_token');
+    sessionStorage.removeItem('link_account_uuid');
+    sessionStorage.removeItem('link_account_email');
+    sessionStorage.removeItem('link_account_username');
+    sessionStorage.removeItem('link_account_created_at');
+    sessionStorage.removeItem('link_account_is_active');
+    sessionStorage.removeItem('link_account_is_admin');
+  };
+
+  $(window).on("load", function() {
+    update_visibility();
+  });
+
+  function do_login(dir) {
+    sessionStorage.setItem('link_account_api_token', '<%= Thread.current[:arvados_api_token] %>');
+    sessionStorage.setItem('link_account_email', '<%= Thread.current[:user].email %>');
+    sessionStorage.setItem('link_account_username', '<%= Thread.current[:user].username %>');
+    sessionStorage.setItem('link_account_uuid', '<%= Thread.current[:user].uuid %>');
+    sessionStorage.setItem('link_account_created_at', '<%= Thread.current[:user].created_at %>');
+    sessionStorage.setItem('link_account_is_active', <%= if Thread.current[:user].is_active then "true" else "false" end %>);
+    sessionStorage.setItem('link_account_is_admin', <%= if Thread.current[:user].is_admin then "true" else "false" end %>);
+    window.location.replace('<%=arvados_api_client.arvados_logout_url(return_to: arvados_api_client.arvados_login_url(return_to: "#{strip_token_from_path(request.url)}?direction="))%>'+dir);
+  }
+
+  $(document).on("click", "#link-account-in", function(e) { do_login("in"); });
+  $(document).on("click", "#link-account-out", function(e) { do_login("out"); });
+
+  $(document).on("click", "#cancel-link-accounts", function() {
+    window.location.replace('/users/link_account?api_token='+$("#new-user-token-input").val());
+  });
+<% end %>
+
+<div id="need-login" style="display: none">
+
+  <p>You are currently logged in as <b><%= Thread.current[:user].email %></b> (<%= Thread.current[:user].username%>, <%= Thread.current[:user].uuid %>) created at <b><%= Thread.current[:user].created_at%></b></p>
+
+<p>You can link Arvados accounts.  After linking, either login will take you to the same account.</p>
+
+  <p>
+    <% if Thread.current[:user].is_active %>
+  <button class="btn btn-primary" id="link-account-in" style="margin-right: 1em">
+    <i class="fa fa-fw fa-sign-in"></i> Add another login to this account
+  </button>
+  <% end %>
+  <button class="btn btn-primary" id="link-account-out" style="margin-right: 1em">
+    <i class="fa fa-fw fa-sign-in"></i> Use this login to access another account
+  </button>
+
+</p>
+</div>
+
+<div id="ready-to-link" style="display: none">
+
+  <div id="will-link-to"></div>
+
+  <%= button_tag "Cancel", class: "btn btn-cancel pull-left", id: "cancel-link-accounts", style: "margin-right: 1em" %>
+
+  <%= form_tag do |f| %>
+    <input type="hidden" id="new-user-token-input" name="new_user_token" value="" />
+    <input type="hidden" id="new-user-token-input" name="direction" value="<%=params[:direction]%>" />
+    <%= button_tag class: "btn btn-primary", id: "link-account-submit" do %>
+      <i class="fa fa-fw fa-link"></i> Link accounts
+  <% end %>
+<% end %>
+
+</div>
+</div>
index d969abd78c2b69d8de936e2a00df0c0d1f1ef0f1..718adfd2ed0583a99f8eebb221b5eae0c7d012c3 100644 (file)
@@ -65,6 +65,8 @@ ArvadosWorkbench::Application.routes.draw do
     get 'virtual_machines', :on => :member
     get 'repositories', :on => :member
     get 'ssh_keys', :on => :member
+    get 'link_account', :on => :collection
+    post 'link_account', :on => :collection, :action => :merge
   end
   get '/current_token' => 'users#current_token'
   get "/add_ssh_key_popup" => 'users#add_ssh_key_popup', :as => :add_ssh_key_popup
@@ -109,7 +111,7 @@ ArvadosWorkbench::Application.routes.draw do
     get 'tab_counts', on: :member
     get 'public', on: :collection
   end
-  
+
   resources :search do
     get 'choose', :on => :collection
   end
@@ -131,9 +133,9 @@ ArvadosWorkbench::Application.routes.draw do
   match '/_health/ping', to: 'healthcheck#ping', via: [:get]
 
   get '/tests/mithril', to: 'tests#mithril'
-  
+
   get '/status', to: 'status#status'
-  
+
   # Send unroutable requests to an arbitrary controller
   # (ends up at ApplicationController#render_not_found)
   match '*a', to: 'links#render_not_found', via: [:get, :post]
diff --git a/apps/workbench/test/integration/link_account_test.rb b/apps/workbench/test/integration/link_account_test.rb
new file mode 100644 (file)
index 0000000..9c22f5a
--- /dev/null
@@ -0,0 +1,172 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
+require 'integration_helper'
+require 'webrick'
+
+class LinkAccountTest < ActionDispatch::IntegrationTest
+  setup do
+    need_javascript
+  end
+
+  def start_sso_stub token
+    port = available_port('sso_stub')
+
+    s = WEBrick::HTTPServer.new(
+      :Port => port,
+      :BindAddress => 'localhost',
+      :Logger => WEBrick::Log.new('/dev/null', WEBrick::BasicLog::DEBUG),
+      :AccessLog => [nil,nil]
+    )
+
+    s.mount_proc("/login"){|req, res|
+      res.set_redirect(WEBrick::HTTPStatus::TemporaryRedirect, req.query["return_to"] + "&api_token=#{token}")
+      s.shutdown
+    }
+
+    s.mount_proc("/logout"){|req, res|
+      res.set_redirect(WEBrick::HTTPStatus::TemporaryRedirect, req.query["return_to"])
+    }
+
+    Thread.new do
+      s.start
+    end
+
+    "http://localhost:#{port}/"
+  end
+
+  test "Add another login to this account" do
+    visit page_with_token('active_trustedclient')
+    stub = start_sso_stub(api_fixture('api_client_authorizations')['project_viewer_trustedclient']['api_token'])
+    Rails.configuration.arvados_login_base = stub + "login"
+
+    find("#notifications-menu").click
+    assert_text "active-user@arvados.local"
+
+    find("a", text: "Link account").click
+    find("button", text: "Add another login to this account").click
+
+    find("#notifications-menu").click
+    assert_text "project-viewer@arvados.local"
+
+    find("button", text: "Link accounts").click
+
+    find("#notifications-menu").click
+    assert_text "active-user@arvados.local"
+  end
+
+  test "Use this login to access another account" do
+    visit page_with_token('project_viewer_trustedclient')
+    stub = start_sso_stub(api_fixture('api_client_authorizations')['active_trustedclient']['api_token'])
+    Rails.configuration.arvados_login_base = stub + "login"
+
+    find("#notifications-menu").click
+    assert_text "project-viewer@arvados.local"
+
+    find("a", text: "Link account").click
+    find("button", text: "Use this login to access another account").click
+
+    find("#notifications-menu").click
+    assert_text "active-user@arvados.local"
+
+    find("button", text: "Link accounts").click
+
+    find("#notifications-menu").click
+    assert_text "active-user@arvados.local"
+  end
+
+  test "Link login of inactive user to this account" do
+    visit page_with_token('active_trustedclient')
+    stub = start_sso_stub(api_fixture('api_client_authorizations')['inactive_uninvited_trustedclient']['api_token'])
+    Rails.configuration.arvados_login_base = stub + "login"
+
+    find("#notifications-menu").click
+    assert_text "active-user@arvados.local"
+
+    find("a", text: "Link account").click
+    find("button", text: "Add another login to this account").click
+
+    find("#notifications-menu").click
+    assert_text "inactive-uninvited-user@arvados.local"
+
+    find("button", text: "Link accounts").click
+
+    find("#notifications-menu").click
+    assert_text "active-user@arvados.local"
+  end
+
+  test "Cannot link to inactive user" do
+    visit page_with_token('active_trustedclient')
+    stub = start_sso_stub(api_fixture('api_client_authorizations')['inactive_uninvited_trustedclient']['api_token'])
+    Rails.configuration.arvados_login_base = stub + "login"
+
+    find("#notifications-menu").click
+    assert_text "active-user@arvados.local"
+
+    find("a", text: "Link account").click
+    find("button", text: "Use this login to access another account").click
+
+    find("#notifications-menu").click
+    assert_text "inactive-uninvited-user@arvados.local"
+
+    assert_text "Cannot link active-user@arvados.local"
+
+    assert find("#link-account-submit")['disabled']
+
+    find("button", text: "Cancel").click
+
+    find("#notifications-menu").click
+    assert_text "active-user@arvados.local"
+  end
+
+  test "Inactive user can link to active account" do
+    visit page_with_token('inactive_uninvited_trustedclient')
+    stub = start_sso_stub(api_fixture('api_client_authorizations')['active_trustedclient']['api_token'])
+    Rails.configuration.arvados_login_base = stub + "login"
+
+    find("#notifications-menu").click
+    assert_text "inactive-uninvited-user@arvados.local"
+
+    assert_text "Already have an account with a different login?"
+
+    find("a", text: "Link this login to your existing account").click
+
+    assert_no_text "Add another login to this account"
+
+    find("button", text: "Use this login to access another account").click
+
+    find("#notifications-menu").click
+    assert_text "active-user@arvados.local"
+
+    find("button", text: "Link accounts").click
+
+    find("#notifications-menu").click
+    assert_text "active-user@arvados.local"
+  end
+
+  test "Admin cannot link to non-admin" do
+    visit page_with_token('admin_trustedclient')
+    stub = start_sso_stub(api_fixture('api_client_authorizations')['active_trustedclient']['api_token'])
+    Rails.configuration.arvados_login_base = stub + "login"
+
+    find("#notifications-menu").click
+    assert_text "admin@arvados.local"
+
+    find("a", text: "Link account").click
+    find("button", text: "Use this login to access another account").click
+
+    find("#notifications-menu").click
+    assert_text "active-user@arvados.local"
+
+    assert_text "Cannot link admin account admin@arvados.local"
+
+    assert find("#link-account-submit")['disabled']
+
+    find("button", text: "Cancel").click
+
+    find("#notifications-menu").click
+    assert_text "admin@arvados.local"
+  end
+
+end
index bd457872fd09737a02d051412fa7c738b5dea73d..fa1a260c3a225fcbf29bfccb17e364b1f7277000 100644 (file)
@@ -41,9 +41,9 @@ centos7|pbr|0.11.1|2|python|all
 centos7|pyparsing|2.1.10|2|python|all
 centos7|keepalive|0.5|2|python|all
 debian8,debian9,ubuntu1404,ubuntu1604,centos7|lockfile|0.12.2|2|python|all|--epoch 1
-debian8,debian9,ubuntu1404,ubuntu1604,centos7|subprocess32|3.5.0rc1|2|python|all
+debian8,debian9,ubuntu1404,ubuntu1604,centos7|subprocess32|3.5.1|2|python|all
 all|ruamel.yaml|0.14.12|2|python|amd64|--python-setup-py-arguments --single-version-externally-managed
-all|cwltest|1.0.20180416154033|4|python|all|--depends 'python-futures >= 3.0.5' --depends 'python-subprocess32'
+all|cwltest|1.0.20180518074130|4|python|all|--depends 'python-futures >= 3.0.5' --depends 'python-subprocess32 >= 3.5.0'
 all|junit-xml|1.8|3|python|all
 all|rdflib-jsonld|0.4.0|2|python|all
 all|futures|3.0.5|2|python|all
index f087188991c5c06a3b19a3b1e38325d9d29e5c52..2a40b50ec1f5b94c2523e293871d04005d962973 100755 (executable)
@@ -96,7 +96,7 @@ do
             | */nodemanager/doc/*.cfg \
             | */nodemanager/tests/fake*.cfg.template \
             | */nginx.conf \
-            | build/build.list)
+            | build/build.list | *.R)
             fixer=fixer
             cc="#"
             ;;
@@ -175,7 +175,7 @@ ${cc}${cc:+ }SPDX-License-Identifier: CC-BY-SA-3.0${ce}"
     wantBYSAmd="[comment]: # (Copyright Â© The Arvados Authors. All rights reserved.)
 [comment]: # ()
 [comment]: # (SPDX-License-Identifier: CC-BY-SA-3.0)"
-    found=$(head -n20 "$fnm" | egrep -A${grepAfter} -B${grepBefore} 'Copyright.*Arvados' || true)
+    found=$(head -n20 "$fnm" | egrep -A${grepAfter} -B${grepBefore} 'Copyright.*All rights reserved.' || true)
     case ${fnm} in
         Makefile | build/* | lib/* | tools/* | apps/* | services/* | sdk/cli/bin/crunch-job)
             want=${wantGPL}
index 0bf68f485a09499499667eba0d57e99a56f09c0b..351d1b2a1f3df666c7fc90df6f456c10522c2dcf 100755 (executable)
@@ -352,7 +352,7 @@ else
 fi
 test_package_presence ${PYTHON2_PKG_PREFIX}-arvados-cwl-runner "$arvados_cwl_runner_version" python "$arvados_cwl_runner_iteration"
 if [[ "$?" == "0" ]]; then
-  fpm_build $WORKSPACE/sdk/cwl "${PYTHON2_PKG_PREFIX}-arvados-cwl-runner" 'Curoverse, Inc.' 'python' "$arvados_cwl_runner_version" "--url=https://arvados.org" "--description=The Arvados CWL runner" --depends "${PYTHON2_PKG_PREFIX}-setuptools" --depends "${PYTHON2_PKG_PREFIX}-subprocess32 >= 3.5.0rc1" --depends "${PYTHON2_PKG_PREFIX}-pathlib2" --depends "${PYTHON2_PKG_PREFIX}-scandir" "${iterargs[@]}"
+  fpm_build $WORKSPACE/sdk/cwl "${PYTHON2_PKG_PREFIX}-arvados-cwl-runner" 'Curoverse, Inc.' 'python' "$arvados_cwl_runner_version" "--url=https://arvados.org" "--description=The Arvados CWL runner" --depends "${PYTHON2_PKG_PREFIX}-setuptools" --depends "${PYTHON2_PKG_PREFIX}-subprocess32 >= 3.5.0" --depends "${PYTHON2_PKG_PREFIX}-pathlib2" --depends "${PYTHON2_PKG_PREFIX}-scandir" "${iterargs[@]}"
 fi
 
 # schema_salad. This is a python dependency of arvados-cwl-runner,
index 78b11b769206fb49b5a1dbe50f2b282933a734af..aaa09e5f541e5e1b400d66b72e8549c55f1821e0 100644 (file)
@@ -50,6 +50,7 @@ navbar:
       - user/cwl/cwl-extensions.html.textile.liquid
       - user/topics/arv-docker.html.textile.liquid
     - Reference:
+      - user/topics/link-accounts.html.textile.liquid
       - user/reference/cookbook.html.textile.liquid
     - Arvados License:
       - user/copying/copying.html.textile.liquid
@@ -148,6 +149,7 @@ navbar:
       - admin/upgrading.html.textile.liquid
       - install/cheat_sheet.html.textile.liquid
       - user/topics/arvados-sync-groups.html.textile.liquid
+      - admin/migrating-providers.html.textile.liquid
       - admin/merge-remote-account.html.textile.liquid
       - install/migrate-docker19.html.textile.liquid
   installguide:
diff --git a/doc/admin/migrating-providers.html.textile.liquid b/doc/admin/migrating-providers.html.textile.liquid
new file mode 100644 (file)
index 0000000..9231dc2
--- /dev/null
@@ -0,0 +1,41 @@
+---
+layout: default
+navsection: admin
+title: "Migrating account providers"
+...
+{% comment %}
+Copyright (C) The Arvados Authors. All rights reserved.
+
+SPDX-License-Identifier: CC-BY-SA-3.0
+{% endcomment %}
+
+This page describes how to enable users to use more than one provider to log into the same Arvados account.  This can be used to migrate account providers, for example, from LDAP to Google.  In order to do this, users must be able to log into both the "old" and "new" providers.
+
+h2. Configure multiple providers in SSO
+
+In @application.yml@ for the SSO server, enable both @google_oauth2@ and @ldap@ providers:
+
+<pre>
+production:
+  google_oauth2_client_id: abcd
+  google_oauth2_client_secret: abcd
+
+  use_ldap:
+    title: Example LDAP
+    host: ldap.example.com
+    port: 636
+    method: ssl
+    base: "ou=Users, dc=example, dc=com"
+    uid: uid
+    username: uid
+</pre>
+
+Restart the SSO server after changing the configuration.
+
+h2. Link accounts
+
+Instruct users to go through the process of "linking accounts":{{site.baseurl}}/user/topics/link-accounts.html
+
+After linking accounts, users can use the new provider to access their existing Arvados account.
+
+Once all users have migrated, the old account provider can be removed from the SSO configuration.
index f5245b38685812af85d9342d56fbbb03721f16a2..0bd2ec7f0c4a55ee8755643c4d4cc22a4a2935ec 100644 (file)
@@ -1,3 +1,7 @@
+/* Copyright (C) The Arvados Authors. All rights reserved.
+
+SPDX-License-Identifier: CC-BY-SA-3.0 */
+
 img.full-width {
     width: 100%
 }
diff --git a/doc/user/topics/link-accounts.html.textile.liquid b/doc/user/topics/link-accounts.html.textile.liquid
new file mode 100644 (file)
index 0000000..3854bf6
--- /dev/null
@@ -0,0 +1,38 @@
+---
+layout: default
+navsection: userguide
+title: "Linking alternate login accounts"
+...
+{% comment %}
+Copyright (C) The Arvados Authors. All rights reserved.
+
+SPDX-License-Identifier: CC-BY-SA-3.0
+{% endcomment %}
+
+This page describes how to link additional login accounts to the same Arvados account.  This can be used to migrate login accounts, for example, from one Google account to another.  It can also be used to migrate login providers, for example from LDAP to Google.  In order to do this, you must be able to log into both the "old" and "new" accounts.
+
+h2. Link accounts
+
+Follow this process to link the "new" login to the "old" login.
+
+# Log in using the "old" account
+# Under the users menu, choose *Link account*
+# On the link accounts page, press the button *Add another login to this account*
+# Follow login instructions from the login provider (eg Google)
+# You will be returned to the *Link accounts* confirmation page.
+# Press the *Link account* button to confirm.
+# After the accounts are linked, you will be returned to the dashboard.
+# Both the "old" and "new" logins will now log in to the same Arvados account.
+
+h2. Link accounts (alternate flow)
+
+You can also link accounts starting with logging into the "new" account first.
+
+# Log in using the "new" account
+# Under the users menu, choose *Link account* (if the user is inactive, there will be a link on the inactive user page)
+# On the link accounts page, press the button *Use this login to access another account*
+# Follow login instructions from the login provider (eg Google)
+# You will be returned to the *Link accounts* confirmation page.
+# Press the *Link account* button to confirm.
+# After the accounts are linked, you will be returned to the dashboard.
+# Both the "old" and "new" logins will now log in to the same Arvados account.
index 644725c2e2b42f05fca8d13c20e809f53d59ed5c..0ec2d115295749067ceb4ee105245aad73df149f 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 #' users.get
 #' 
 #' users.get is a method defined in Arvados class.
index e28ba9606cfebd95a89a436b7ac9953c98d63fb8..8f737831c4634cc09a3121a86e04dcbf0361946b 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 source("./R/util.R")
 
 #' ArvadosFile
index fad452ac7a05e97ab1ceabac0696028085426c9e..e23da138329786cba49e3a8001479461dd30be77 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 source("./R/Subcollection.R")
 source("./R/ArvadosFile.R")
 source("./R/RESTService.R")
index 91e4ec86459dc8e4ad8891d59cbdb80d771a4013..8686f88c1a8a3c55b695351b9993df55939d0f1a 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 source("./R/Subcollection.R")
 source("./R/ArvadosFile.R")
 source("./R/util.R")
index 5df8287fdce7b85f2b83003ac7e55720afc39645..8ce68f3837f158486534c6adc55e4ff23e9386e1 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 HttpParser <- R6::R6Class(
 
     "HttrParser",
index bc6b4d406d1f801acb7328a5d9263b44a473b2a7..95dd375debe5ce076638c55de49a57db1f2d8f0d 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 source("./R/util.R")
 
 HttpRequest <- R6::R6Class(
index dacf88a8c4bff2336e232fba4a86567a2cc5d7af..ac65d0df3f37b6baa6031bc8cbab71b163e27a76 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 RESTService <- R6::R6Class(
 
     "RESTService",
@@ -37,8 +41,8 @@ RESTService <- R6::R6Class(
 
                 headers <- list(Authorization = paste("OAuth2", self$token))
 
-                serverResponse <- self$http$execute("GET", discoveryDocumentURL, headers,
-                                                    retryTimes = self$numRetries)
+                serverResponse <- self$http$exec("GET", discoveryDocumentURL, headers,
+                                                 retryTimes = self$numRetries)
 
                 discoveryDocument <- self$httpParser$parseJSONResponse(serverResponse)
                 private$webDavHostName <- discoveryDocument$keepWebServiceUrl
@@ -64,8 +68,8 @@ RESTService <- R6::R6Class(
                               uuid, "/", relativePath);
             headers <- list(Authorization = paste("OAuth2", self$token)) 
 
-            serverResponse <- self$http$execute("DELETE", fileURL, headers,
-                                                retryTimes = self$numRetries)
+            serverResponse <- self$http$exec("DELETE", fileURL, headers,
+                                             retryTimes = self$numRetries)
 
             if(serverResponse$status_code < 200 || serverResponse$status_code >= 300)
                 stop(paste("Server code:", serverResponse$status_code))
@@ -82,8 +86,8 @@ RESTService <- R6::R6Class(
             headers <- list("Authorization" = paste("OAuth2", self$token),
                            "Destination" = toURL)
 
-            serverResponse <- self$http$execute("MOVE", fromURL, headers,
-                                                retryTimes = self$numRetries)
+            serverResponse <- self$http$exec("MOVE", fromURL, headers,
+                                             retryTimes = self$numRetries)
 
             if(serverResponse$status_code < 200 || serverResponse$status_code >= 300)
                 stop(paste("Server code:", serverResponse$status_code))
@@ -98,8 +102,8 @@ RESTService <- R6::R6Class(
 
             headers <- list("Authorization" = paste("OAuth2", self$token))
 
-            response <- self$http$execute("PROPFIND", collectionURL, headers,
-                                          retryTimes = self$numRetries)
+            response <- self$http$exec("PROPFIND", collectionURL, headers,
+                                       retryTimes = self$numRetries)
 
             if(all(response == ""))
                 stop("Response is empty, request may be misconfigured")
@@ -119,8 +123,8 @@ RESTService <- R6::R6Class(
 
             headers <- list("Authorization" = paste("OAuth2", self$token))
 
-            response <- self$http$execute("PROPFIND", subcollectionURL, headers,
-                                          retryTimes = self$numRetries)
+            response <- self$http$exec("PROPFIND", subcollectionURL, headers,
+                                       retryTimes = self$numRetries)
 
             if(all(response == ""))
                 stop("Response is empty, request may be misconfigured")
@@ -156,8 +160,8 @@ RESTService <- R6::R6Class(
             if(!(contentType %in% self$httpParser$validContentTypes))
                 stop("Invalid contentType. Please use text or raw.")
 
-            serverResponse <- self$http$execute("GET", fileURL, headers,
-                                                retryTimes = self$numRetries)
+            serverResponse <- self$http$exec("GET", fileURL, headers,
+                                             retryTimes = self$numRetries)
 
             if(serverResponse$status_code < 200 || serverResponse$status_code >= 300)
                 stop(paste("Server code:", serverResponse$status_code))
@@ -173,8 +177,8 @@ RESTService <- R6::R6Class(
                             "Content-Type" = contentType)
             body <- content
 
-            serverResponse <- self$http$execute("PUT", fileURL, headers, body,
-                                                retryTimes = self$numRetries)
+            serverResponse <- self$http$exec("PUT", fileURL, headers, body,
+                                             retryTimes = self$numRetries)
 
             if(serverResponse$status_code < 200 || serverResponse$status_code >= 300)
                 stop(paste("Server code:", serverResponse$status_code))
@@ -210,8 +214,8 @@ RESTService <- R6::R6Class(
                             "Content-Type" = contentType)
             body <- NULL
 
-            serverResponse <- self$http$execute("PUT", fileURL, headers, body,
-                                                retryTimes = self$numRetries)
+            serverResponse <- self$http$exec("PUT", fileURL, headers, body,
+                                             retryTimes = self$numRetries)
 
             if(serverResponse$status_code < 200 || serverResponse$status_code >= 300)
                 stop(paste("Server code:", serverResponse$status_code))
index b3b01f89c18eb07deabbefced545e34ad1f1b18f..60714a4ad835b9bc201fb780bb38b5fb8a81461c 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 source("./R/util.R")
 
 #' Subcollection
index 6db28f9dac63c070df26023da66c87e9b0124d67..3e8c2fa0cf2b1494c33a7246a9f97a8669a3b514 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 getAPIDocument <- function(){
     url <- "https://4xphq.arvadosapi.com/discovery/v1/apis/arvados/v1/rest"
     serverResponse <- httr::RETRY("GET", url = url)
index 57dd75f228ea410800897b60031d5734ca215f85..f796cb7b87eca67b3de28d5929221d792554b047 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 #' listAll
 #'
 #' List all resources even if the number of items is greater than maximum API limit.
index cfa0ce5ae6d94b79f16b7fb7ebd6e6dc035f8cf4..c2fe07859330ca1db4724ef6683fe7707e851a6f 100644 (file)
@@ -1,3 +1,7 @@
+[comment]: # (Copyright Â© The Arvados Authors. All rights reserved.)
+[comment]: # ()
+[comment]: # (SPDX-License-Identifier: CC-BY-SA-3.0)
+
 ## R SDK for Arvados
 
 This SDK focuses on providing support for accessing Arvados projects, collections, and the files within collections.
index 73e088ecb61f1e299b1a801330448a42f8cf02cc..5decab9af3c8ff185dc9278e96a26be7462924c2 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 #Run script with $Rscript createDoc.R input.Rmd output.html
 
 require(knitr) # required for knitting from rmd to md
index 5314c86e28b0d3d7352733943019a3602dbf019b..593129bb3ceeb18bbc6cb2520529fd5067b823b1 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 options(repos=structure(c(CRAN="http://cran.wustl.edu/")))
 if (!requireNamespace("devtools")) {
   install.packages("devtools")
index 1f8931d917969115a382b61b2ac378e47e665764..156dde1080c5040373d55633ff8a689a8867484a 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 results <- devtools::test()
 any_error <- any(as.data.frame(results)$error)
 if (any_error) {
index 18ef411fd644144ac34ba76203f8bc6d8f793f17..9ca4f86fb67d76b3a0abc0b16734788e6fff882b 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 library(testthat)
 library(ArvadosR)
 
index 5886ff761f6d0b3b586397ac36562f9aa385eb0d..4fcfd6c67e53f12c8bbd9908d750b1a52c756e07 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 FakeArvados <- R6::R6Class(
 
     "FakeArvados",
index 865234d83552db7965f1d4077085a9f83c65ec4e..c97572c193f1eadbd315928fb09d56aff5e2d7a2 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 FakeHttpParser <- R6::R6Class(
 
     "FakeHttrParser",
index 533602886ab09e0d34a49e2829acaf73a9051baa..2633abdf2c745bf0e4c9afcee1b73b7c5751fbeb 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 FakeHttpRequest <- R6::R6Class(
 
     "FakeHttpRequest",
@@ -56,8 +60,8 @@ FakeHttpRequest <- R6::R6Class(
             self$serverMaxElementsPerRequest <- 5
         },
 
-        execute = function(verb, url, headers = NULL, body = NULL, query = NULL,
-                           limit = NULL, offset = NULL, retryTimes = 0)
+        exec = function(verb, url, headers = NULL, body = NULL, query = NULL,
+                        limit = NULL, offset = NULL, retryTimes = 0)
         {
             private$validateURL(url)
             private$validateHeaders(headers)
index d370e87fbe7e3ca581e4a36ac3a2a149989f18e1..08e8717de5e4b97b5776c2c6cc8893c523f4c133 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 FakeRESTService <- R6::R6Class(
 
     "FakeRESTService",
index 0c1db1a6417eb65cac844428b9dc6bdc44fbb0b5..fb14888aab91b982d88dbdddca0be9589f757fb8 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 source("fakes/FakeRESTService.R")
 
 context("ArvadosFile")
index ec00ca3c66dbcc66d875abee3d810a9ba06b9cd9..c3c70910e4c63acea6d86f5df71cc9bab9f3e72f 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 source("fakes/FakeRESTService.R")
 
 context("Collection")
index 42a54bf69422a31235768488ff2839716011d25d..5c8a40526988bb562c45b5702fd921a743f0a77c 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 context("CollectionTree")
 
 test_that("constructor creates file tree from character array properly", {
index b2862128c261f9cf8b8634ebcc384fe3113a286d..a119d88bf82fa226e26d5127f3ae001d1b515a2e 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 context("Http Parser")
 
 
index 5c2fb602fdb1edb1bdee38e93b831a35c34bb6f0..5ad8aa03115207035ee7f369ded5fbcd597e0ba7 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 context("Http Request")
 
 
index 898e59e3be89ef1c71aba81aca223b8d581c31a9..859b6180f3380c2d834b99e126aa0c7761155368 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 source("fakes/FakeArvados.R")
 source("fakes/FakeHttpRequest.R")
 source("fakes/FakeHttpParser.R")
index b2b0bc9ccfecc66998e43f7d7b6de40c31a6d68e..e025586c58a968f6c0d61a47512087a69d601635 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 source("fakes/FakeRESTService.R")
 
 context("Subcollection")
index ea091517c6b8abdf2f777e46046d8786623d1edc..9f5e07c1767af6c089274a308dc3dc270fb25c2f 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 context("Utility function")
 
 test_that("listAll always returns all resource items from server", {
index 8268300e75b66d6f82b999f649003ec3a0615bbf..fea6adfacc323539d7c2cd595f66d441859893b8 100644 (file)
@@ -39,6 +39,7 @@ class ArvadosCommandTool(CommandLineTool):
         # Workaround for #13365
         builderargs = kwargs.copy()
         builderargs["toplevel"] = True
+        builderargs["tmp_outdir_prefix"] = ""
         builder = self._init_job(joborder, **builderargs)
         joborder = builder.job
 
index 696837a366e51672eb8fbda1abf34c083da7f466..4df89ee75583f55a4c7d7cafb5b404dfe2c08467 100644 (file)
@@ -33,7 +33,7 @@ setup(name='arvados-cwl-runner',
       # Note that arvados/build/run-build-packages.sh looks at this
       # file to determine what version of cwltool and schema-salad to build.
       install_requires=[
-          'cwltool==1.0.20180508202931',
+          'cwltool==1.0.20180522135731',
           'schema-salad==2.7.20180501211602',
           'typing==3.5.3.0',
           'ruamel.yaml >=0.13.11, <0.15',
index ddc7ff9588c67d396b28226d344a493a7a281200..a7445449af6030e7afee4bdb524ac55afc90b8ec 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 cwlVersion: v1.0
 class: ExpressionTool
 requirements:
index 5c5571ab264097ad6ca1cee8196b00ccf92da22e..60c765788c3fad8233b5be760335407656ca13b4 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 dir:
   class: Directory
   location: samples
\ No newline at end of file
index 8c28cc2215b5d3eac21b94ad31292595dae25f43..e4730cfc76982e142cae4a9b52369e65be135af8 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 cwlVersion: v1.0
 class: CommandLineTool
 requirements:
index 3f1e8902cc5a00488315d13cdb81a40f90000bb1..343df0bbda5c40f945f203a37d032ac154cde75a 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 cwlVersion: v1.0
 class: Workflow
 requirements:
index 6c9e7f760c05c2fbeb67ae98573128709b3b0dd0..f5e5e702285e1ee545048a8b0f2ea54a61f645cf 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 {
    "cwlVersion": "v1.0",
       "arguments": [
index b37990aa9d78fb5f2efbbf93f9996dc013aa9cb0..6c49757bbe630625dcc84e01589bd917338d83e7 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 cwlVersion: v1.0
 class: CommandLineTool
 inputs:
index 5d2c699022cfc4262b6f55e55551e726a7bc8590..19e4077e8a1e0965d331db2b958d6690e6c6e2e1 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 cwlVersion: v1.0
 class: Workflow
 $namespaces:
index 8b9dd83031aa530340026b8be623db52442bf421..7eb6bcee6de4e01615be0699901dd539f8ccafe2 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 i:
   class: File
   location: keep:f225e6259bdd63bc7240599648dde9f1+97/hg19.fa
index 248aefd2c6ef9ad482f22be3f5c771a73b1e45b7..5539562070ff2c226b8188f66df8a452af7f59a8 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 cwlVersion: v1.0
 class: Workflow
 $namespaces:
index 540edcf4f0dee20f99de723b32b276ab284bf403..20847d44900e630663ca1dfcc42137ae3114c98e 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 class: Workflow
 cwlVersion: v1.0
 inputs:
index 892973b5752b551f22c77cd84f977199398ac2dd..29f58f09c5085ecf9a344f38ba792d37c95f1548 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 class: CommandLineTool
 cwlVersion: v1.0
 inputs:
index 1107623925331bc231af23c9b139834752106883..2e61ee3e8c13c90bda45a30b5894615fcc6e868e 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 toplevel_input:
   class: File
   location: keep:4d8a70b1e63b2aad6984e40e338e2373+69/hello.txt
\ No newline at end of file
index 883d24e857342d98a04ff59ba9d3fe80978d78f2..254ed91b81abaf5de60d6b90de4613f3c10d35ed 100644 (file)
@@ -1 +1,5 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 pw: blorp
index 6d2598edaa8e4bdf2894c83a106c171d34fd1937..abf96947eb1b40f21207e0fc9171f66482a0bd47 100644 (file)
@@ -346,7 +346,7 @@ class TestWorkflow(unittest.TestCase):
                                               basedir="", make_fs_access=make_fs_access, loader=document_loader,
                                               makeTool=runner.arv_make_tool, metadata=metadata)
         arvtool.formatgraph = None
-        it = arvtool.job({}, mock.MagicMock(), basedir="", make_fs_access=make_fs_access)
+        it = arvtool.job({}, mock.MagicMock(), basedir="", make_fs_access=make_fs_access, tmp_outdir_prefix="")
         it.next().run()
         it.next().run()
 
@@ -432,7 +432,7 @@ class TestWorkflow(unittest.TestCase):
                                               basedir="", make_fs_access=make_fs_access, loader=document_loader,
                                               makeTool=runner.arv_make_tool, metadata=metadata)
         arvtool.formatgraph = None
-        it = arvtool.job({}, mock.MagicMock(), basedir="", make_fs_access=make_fs_access)
+        it = arvtool.job({}, mock.MagicMock(), basedir="", make_fs_access=make_fs_access, tmp_outdir_prefix="")
         it.next().run()
         it.next().run()
 
index ed09e6e27f6ed26c05b1671b0b2a19cfe994a987..fdd56bedfd0610f995661f73dd73e688c484a9bf 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 cwlVersion: v1.0
 class: CommandLineTool
 inputs:
index c8264647161cf412fd20cbcd08febf01ca92154f..98931ab7eceb74e6429e663ff52b34fdb984e4f4 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 cwlVersion: v1.0
 class: CommandLineTool
 inputs:
index ab7b0a4c6f345711d5cc1ce550edb97f803ba7d6..3d0fe224f261a19245977444ecd308dffb907a55 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 cwlVersion: v1.0
 class: CommandLineTool
 inputs:
index cd57ff34cd98515176d9498639d2762ab189fd5c..8bfc5d63f744a784e13d78ec48049211ae629c48 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 cwlVersion: v1.0
 class: Workflow
 inputs: []
index d4f667c03443a18aea9b4e5f36e32a335cf991b8..2e66b1013d2f435d7d6232e8e10f4180e3c6b3f1 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 cwlVersion: v1.0
 class: Workflow
 inputs: []
index 597ea96da35cd82f70ac3e3424f727655117b4f2..f779aef8cf6d7ab6d24c49b22b568ffb91e638d2 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 cwlVersion: v1.0
 class: Workflow
 inputs: []
index 76437a29825398846b6afd9a2ae830b23ca7d72f..ccc0ceb7325105184465b0b88f61eedcc1c4d3cc 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 cwlVersion: v1.0
 class: CommandLineTool
 inputs:
index 4e6372b62048fad5a14e259d2ad511088402a120..5c74ef0045f03a414b255e56b5a2a81c65923589 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 cwlVersion: v1.0
 class: Workflow
 inputs:
index df9009ada51bd61ab765191bc765b597b0c382fb..4b71c1341542cf0a573a941c240bbc2e4cb49a4f 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 cwlVersion: v1.0
 class: CommandLineTool
 inputs:
index 45faa8937c517d809fff39adae6a62fcf5c70945..0133c7aef13dd068c0074c3d0e650b0116fde6ea 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 cwlVersion: v1.0
 class: Workflow
 inputs: []
index 7ba96eea4d8bd1023c95182b74f0e4502e928acd..ffe8731438109d218a5da2316d05d4376bbeff71 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 cwlVersion: v1.0
 class: Workflow
 inputs: []
index 911650d61fcd53f0068d865cf6637ec8872fdab9..0292d1377c917f58bccb63194825c68a29288188 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 cwlVersion: v1.0
 class: Workflow
 inputs: []
index d6e65afd6c355b75b2e82e6d7656b2f714046364..6e562e43dbd791f390dd25f6803e4a23c49ce967 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 cwlVersion: v1.0
 class: Workflow
 inputs: []
index 631af182b9e46893848cfe2c3619950a02527b32..de2748c7322619b69499ee5e11640815412187bd 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 cwlVersion: v1.0
 class: Workflow
 inputs: []
index bd26cc1d1bd6d5d2b907c7372c07d1663ace963c..6bcf69ed7fcd50561377878d7e1da9efca7a6240 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 cwlVersion: v1.0
 class: Workflow
 inputs: []
index ac07b9dbb21f333b136be5106ab98c72545502c0..715f1efcdfb542065faf11bc208463918a9a3c88 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 cwlVersion: v1.0
 class: Workflow
 inputs: []
index 55b7b19430dc6a05a6ffde432ffb33a8d7aaa8c1..355872232bc7f430a8b61f7e8f8dffbe09cc5530 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 import arvados
 import sys
 import os
index 29dc3d6aea63701cdcedf8d4226127d6a2c24d99..d7c8037588c2ff64e43da8f2e59f991e974141ab 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 cwlVersion: v1.0
 class: Workflow
 requirements:
index 63a543881c230941db6ee45b177ab706e057e384..5cdd80dbdbc7a145d9e712d596b1165c91520dcc 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 cwlVersion: v1.0
 class: Workflow
 $namespaces:
index b7893e221104308771c334c984ea0ad303646eb6..0a734b326355361f6b8ccc4bc296384e58a57d2e 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 cwlVersion: v1.0
 class: CommandLineTool
 requirements:
index 4db11ccdf2eb9ff26dcd5305ee528b418cb065ce..7a052f86cf36168343eab48b3c9738f5da55497e 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 cwlVersion: v1.0
 class: CommandLineTool
 requirements:
index 0ddeb645022374effe499c1f164b7c61834effa0..2be74b2658b1b67ebdb231e22c6204e53aabc96b 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 cwlVersion: v1.0
 class: CommandLineTool
 $namespaces:
index 17c92d678e3edbe9db442ab74e6c4c23fbee8333..05d950d18c08be14ea72ea297585412136f8f198 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 cwlVersion: v1.0
 class: Workflow
 $namespaces:
index 127bee8ddf7c6e641597d92c935353eece84564b..9af8d0ad405828b0c8e9906575cb1b77752eaa63 100644 (file)
@@ -1,3 +1,7 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: Apache-2.0
+
 package arvados
 
 import (
index 297a8617084b3247c7b15c18a8ae9c2176224518..5edb1f95ca86acbfac5bf7dfd961822a33003ee1 100644 (file)
@@ -1,3 +1,7 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: Apache-2.0
+
 package health
 
 import (
index 7e601f2e70211e30b1786edae098380ba89556e6..8a540371cbbf01ffcf7bf1bb97b94713ad303f74 100644 (file)
@@ -1,3 +1,7 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: Apache-2.0
+
 package health
 
 import (
index 8fb90c944396967e6863a38daee27ffe3cb8b9ec..e390a60a87a0977a018025306252d8f86f2e69cc 100644 (file)
@@ -1436,7 +1436,7 @@ class Collection(RichCollectionBase):
     @must_be_writable
     @synchronized
     @retry_method
-    def save(self, merge=True, num_retries=None):
+    def save(self, storage_classes=None, merge=True, num_retries=None):
         """Save collection to an existing collection record.
 
         Commit pending buffer blocks to Keep, merge with remote record (if
@@ -1447,6 +1447,9 @@ class Collection(RichCollectionBase):
         the API server.  If you want to save a manifest to Keep only, see
         `save_new()`.
 
+        :storage_classes:
+          Specify desirable storage classes to be used when writing data to Keep.
+
         :merge:
           Update and merge remote changes before saving.  Otherwise, any
           remote changes will be ignored and overwritten.
@@ -1455,6 +1458,9 @@ class Collection(RichCollectionBase):
           Retry count on API calls (if None,  use the collection default)
 
         """
+        if storage_classes and type(storage_classes) is not list:
+            raise errors.ArgumentError("storage_classes must be list type.")
+
         if not self.committed():
             if not self._has_collection_uuid():
                 raise AssertionError("Collection manifest_locator is not a collection uuid.  Use save_new() for new collections.")
@@ -1465,14 +1471,24 @@ class Collection(RichCollectionBase):
                 self.update()
 
             text = self.manifest_text(strip=False)
+            body={'manifest_text': text}
+            if storage_classes:
+                body["storage_classes_desired"] = storage_classes
+
             self._remember_api_response(self._my_api().collections().update(
                 uuid=self._manifest_locator,
-                body={'manifest_text': text}
+                body=body
                 ).execute(
                     num_retries=num_retries))
             self._manifest_text = self._api_response["manifest_text"]
             self._portable_data_hash = self._api_response["portable_data_hash"]
             self.set_committed(True)
+        elif storage_classes:
+            self._remember_api_response(self._my_api().collections().update(
+                uuid=self._manifest_locator,
+                body={"storage_classes_desired": storage_classes}
+                ).execute(
+                    num_retries=num_retries))
 
         return self._manifest_text
 
@@ -1483,6 +1499,7 @@ class Collection(RichCollectionBase):
     def save_new(self, name=None,
                  create_collection_record=True,
                  owner_uuid=None,
+                 storage_classes=None,
                  ensure_unique_name=False,
                  num_retries=None):
         """Save collection to a new collection record.
@@ -1503,6 +1520,9 @@ class Collection(RichCollectionBase):
           the user, or project uuid that will own this collection.
           If None, defaults to the current user.
 
+        :storage_classes:
+          Specify desirable storage classes to be used when writing data to Keep.
+
         :ensure_unique_name:
           If True, ask the API server to rename the collection
           if it conflicts with a collection with the same name and owner.  If
@@ -1525,6 +1545,10 @@ class Collection(RichCollectionBase):
                     "replication_desired": self.replication_desired}
             if owner_uuid:
                 body["owner_uuid"] = owner_uuid
+            if storage_classes:
+                if type(storage_classes) is not list:
+                    raise errors.ArgumentError("storage_classes must be list type.")
+                body["storage_classes_desired"] = storage_classes
 
             self._remember_api_response(self._my_api().collections().create(ensure_unique_name=ensure_unique_name, body=body).execute(num_retries=num_retries))
             text = self._api_response["manifest_text"]
index 388d87b3a6f99ce51dc6b39248fd6810394828f3..cba00c3c8cf153039de990d27867558d0dbc699a 100644 (file)
@@ -140,6 +140,10 @@ physical storage devices (e.g., disks) should have a copy of each data
 block. Default is to use the server-provided default (if any) or 2.
 """)
 
+upload_opts.add_argument('--storage-classes', help="""
+Specify comma separated list of storage classes to be used when saving data to Keep.
+""")
+
 upload_opts.add_argument('--threads', type=int, metavar='N', default=None,
                          help="""
 Set the number of upload threads to be used. Take into account that
@@ -418,8 +422,8 @@ class ArvPutUploadJob(object):
     def __init__(self, paths, resume=True, use_cache=True, reporter=None,
                  name=None, owner_uuid=None, api_client=None,
                  ensure_unique_name=False, num_retries=None,
-                 put_threads=None, replication_desired=None,
-                 filename=None, update_time=60.0, update_collection=None,
+                 put_threads=None, replication_desired=None, filename=None,
+                 update_time=60.0, update_collection=None, storage_classes=None,
                  logger=logging.getLogger('arvados.arv_put'), dry_run=False,
                  follow_links=True, exclude_paths=[], exclude_names=None):
         self.paths = paths
@@ -439,6 +443,7 @@ class ArvPutUploadJob(object):
         self.replication_desired = replication_desired
         self.put_threads = put_threads
         self.filename = filename
+        self.storage_classes = storage_classes
         self._api_client = api_client
         self._state_lock = threading.Lock()
         self._state = None # Previous run state (file list & manifest)
@@ -614,10 +619,14 @@ class ArvPutUploadJob(object):
                 else:
                     # The file already exist on remote collection, skip it.
                     pass
-            self._remote_collection.save(num_retries=self.num_retries)
+            self._remote_collection.save(storage_classes=self.storage_classes,
+                                         num_retries=self.num_retries)
         else:
+            if self.storage_classes is None:
+                self.storage_classes = ['default']
             self._local_collection.save_new(
                 name=self.name, owner_uuid=self.owner_uuid,
+                storage_classes=self.storage_classes,
                 ensure_unique_name=self.ensure_unique_name,
                 num_retries=self.num_retries)
 
@@ -1045,6 +1054,15 @@ def main(arguments=None, stdout=sys.stdout, stderr=sys.stderr,
     else:
         reporter = None
 
+    #  Split storage-classes argument
+    storage_classes = None
+    if args.storage_classes:
+        storage_classes = args.storage_classes.strip().split(',')
+        if len(storage_classes) > 1:
+            logger.error("Multiple storage classes are not supported currently.")
+            sys.exit(1)
+
+
     # Setup exclude regex from all the --exclude arguments provided
     name_patterns = []
     exclude_paths = []
@@ -1102,6 +1120,7 @@ def main(arguments=None, stdout=sys.stdout, stderr=sys.stderr,
                                  owner_uuid = project_uuid,
                                  ensure_unique_name = True,
                                  update_collection = args.update_collection,
+                                 storage_classes=storage_classes,
                                  logger=logger,
                                  dry_run=args.dry_run,
                                  follow_links=args.follow_links,
index 4b1f69477e5823502b2a5396a586db82a56e6ff7..93cfdc2a36c26389a3259222304e7ba1d5de7dff 100644 (file)
@@ -730,6 +730,11 @@ class ArvadosPutTest(run_test_server.TestCaseWithServers,
                           self.call_main_with_args,
                           ['--project-uuid', self.Z_UUID, '--stream'])
 
+    def test_error_when_multiple_storage_classes_specified(self):
+        self.assertRaises(SystemExit,
+                          self.call_main_with_args,
+                          ['--storage-classes', 'hot,cold'])
+
     def test_error_when_excluding_absolute_path(self):
         tmpdir = self.make_tmpdir()
         self.assertRaises(SystemExit,
@@ -1061,6 +1066,18 @@ class ArvPutIntegrationTest(run_test_server.TestCaseWithServers,
                                        '--project-uuid', self.PROJECT_UUID])
         self.assertEqual(link_name, collection['name'])
 
+    def test_put_collection_with_storage_classes_specified(self):
+        collection = self.run_and_find_collection("", ['--storage-classes', 'hot'])
+
+        self.assertEqual(len(collection['storage_classes_desired']), 1)
+        self.assertEqual(collection['storage_classes_desired'][0], 'hot')
+
+    def test_put_collection_without_storage_classes_specified(self):
+        collection = self.run_and_find_collection("")
+
+        self.assertEqual(len(collection['storage_classes_desired']), 1)
+        self.assertEqual(collection['storage_classes_desired'][0], 'default')
+
     def test_exclude_filename_pattern(self):
         tmpdir = self.make_tmpdir()
         tmpsubdir = os.path.join(tmpdir, 'subdir')
index 49c00191bebe02cc8e267b397212a893a33f246a..a56d4f68f157e0e76534da441c9707a8670960e7 100644 (file)
@@ -1300,17 +1300,29 @@ class CollectionCreateUpdateTest(run_test_server.TestCaseWithServers):
 
     def test_create_and_save(self):
         c = self.create_count_txt()
-        c.save()
+        c.save(storage_classes=['archive'])
+
         self.assertRegex(
             c.manifest_text(),
             r"^\. 781e5e245d69b566979b86e28d23f2c7\+10\+A[a-f0-9]{40}@[a-f0-9]{8} 0:10:count\.txt$",)
+        self.assertEqual(c.api_response()["storage_classes_desired"], ['archive'])
+
 
     def test_create_and_save_new(self):
         c = self.create_count_txt()
-        c.save_new()
+        c.save_new(storage_classes=['archive'])
+
         self.assertRegex(
             c.manifest_text(),
             r"^\. 781e5e245d69b566979b86e28d23f2c7\+10\+A[a-f0-9]{40}@[a-f0-9]{8} 0:10:count\.txt$",)
+        self.assertEqual(c.api_response()["storage_classes_desired"], ['archive'])
+
+    def test_update_storage_classes_desired_if_collection_is_commited(self):
+        c = self.create_count_txt()
+        c.save(storage_classes=['hot'])
+        c.save(storage_classes=['cold'])
+
+        self.assertEqual(c.api_response()["storage_classes_desired"], ['cold'])
 
     def test_create_diff_apply(self):
         c1 = self.create_count_txt()
index 6ec92b0ba66f9a59bc978844563a68d84d20f417..fa29dbd8135453587cee7a7fcfeb220f864d0755 100644 (file)
@@ -20,10 +20,13 @@ class Arvados::V1::ContainersController < ApplicationController
     show
   end
 
-  # Updates use row locking to resolve races between multiple
-  # dispatchers trying to lock the same container.
   def update
-    @object.with_lock do
+    # container updates can trigger container request lookups, which
+    # can deadlock if we don't lock the container_requests table
+    # first.
+    @object.transaction do
+      ActiveRecord::Base.connection.execute('LOCK container_requests, containers IN EXCLUSIVE MODE')
+      @object.reload
       super
     end
   end
index 20633153e758c70f5b91d0b66466a06e6393b2da..b8fe2948923582ad9f40f3ec00c394cd6b2473ec 100644 (file)
@@ -70,7 +70,7 @@ class UserSessionsController < ApplicationController
       end
 
       while (uuid = user.redirect_to_user_uuid)
-        user = User.where(uuid: uuid).first
+        user = User.unscoped.where(uuid: uuid).first
         if !user
           raise Exception.new("identity_url #{omniauth['info']['identity_url']} redirects to nonexistent uuid #{uuid}")
         end
index e9d4f836589cb5933307b869cc6bff8230c894d0..1dbdb571050a70ec3a684f18f335269ac35fd6f8 100644 (file)
@@ -316,11 +316,11 @@ class Container < ArvadosModel
     # (because state might have changed while acquiring the lock).
     check_lock_fail
     transaction do
-      begin
-        reload(lock: 'FOR UPDATE NOWAIT')
-      rescue
-        raise LockFailedError.new("cannot lock: other transaction in progress")
-      end
+      # Locking involves assigning auth_uuid, which involves looking
+      # up container requests, so we must lock both tables in the
+      # proper order to avoid deadlock.
+      ActiveRecord::Base.connection.execute('LOCK container_requests, containers IN EXCLUSIVE MODE')
+      reload
       check_lock_fail
       update_attributes!(state: Locked)
     end
index 831036fd9d9cd722e7e84aa668bb75d5e111d6fc..9d4c20af9faaa1ff7076fdcd0bd8d0348324e4ef 100644 (file)
@@ -414,7 +414,7 @@ class User < ArvadosModel
     end
     if self.is_active_changed?
       if self.is_active != self.is_active_was
-        logger.warn "User #{current_user.uuid} tried to change is_active from #{self.is_admin_was} to #{self.is_admin} for #{self.uuid}"
+        logger.warn "User #{current_user.uuid} tried to change is_active from #{self.is_active_was} to #{self.is_active} for #{self.uuid}"
         self.is_active = self.is_active_was
       end
     end
index dfa08db1a9c82cf01e063f7963e8413bb2cccfd3..707c3dd946f377d7ada7513cf12a1ced9d896487 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
 require 'migrate_yaml_to_json'
 
 class YamlToJson < ActiveRecord::Migration
index 003e5fb0929edfa490e4be82383f2ca7e0b75acb..921803a2970137c89cf8e19c8fdd329061f09c1c 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
 require './db/migrate/20161213172944_full_text_search_indexes'
 
 class JsonCollectionProperties < ActiveRecord::Migration
index d90011c7c1db55ccf9d1c679245155c573fe0cca..aa42423a4dbd7ab9dc2d39ad13ca93e14dba485f 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
 class AddIndexToContainers < ActiveRecord::Migration
   def up
     ActiveRecord::Base.connection.execute("CREATE INDEX index_containers_on_modified_at_uuid ON containers USING btree (modified_at desc, uuid asc)")
index b93dc54fcdb4eb4eba384fd57c5477c0a1a363d4..c9e50a64b79e56a64b1d90cfaee7be2c2dbf8900 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
 class FixTrashFlagFollow < ActiveRecord::Migration
   def change
     ActiveRecord::Base.connection.execute("DROP MATERIALIZED VIEW materialized_permission_view")
index ce2403e743578f272f34cf360dfb544dc6f2132c..0183ef6dc51dc50d81df2999fa8bdd01539f8c14 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
 class AddGinIndexToCollectionProperties < ActiveRecord::Migration
   def up
     ActiveRecord::Base.connection.execute("CREATE INDEX collection_index_on_properties ON collections USING gin (properties);")
index c56b7dcaf730cf715e24f87a717729a243249412..a161f633d8f9a7d5d9a2422650931bbb07c10fec 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
 class AddSecretMountsToContainers < ActiveRecord::Migration
   def change
     add_column :container_requests, :secret_mounts, :jsonb, default: {}
index d577cbbb3eed43473484473d6edcd9f9a55e18b4..529126b299a701617ede80a1ba75fed1b05de280 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
 class ChangeContainerPriorityBigint < ActiveRecord::Migration
   def change
     change_column :containers, :priority, :integer, limit: 8
index b2460ae18299f1cf2b444e1c7757d13b879b1e87..10b35a7aba03f5f5940e0309892567532819b1f2 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
 class AddRedirectToUserUuidToUsers < ActiveRecord::Migration
   def up
     add_column :users, :redirect_to_user_uuid, :string
index 56cafea2c017083a6f86e0299983e7228cfcd372..79e777e0a61132808b56cb8eba1dc80e35e233fb 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
 class AddContainerAuthUuidIndex < ActiveRecord::Migration
   def change
     add_index :containers, :auth_uuid
index 8d9fc53c04d2c6bc4ccaea2009d72a178c6c11c7..92bd7cf872cfeca1c53d38c5ea05d7836e929f4f 100644 (file)
@@ -183,6 +183,13 @@ inactive_uninvited:
   api_token: 62mhllc0otp78v08e3rpa3nsmf8q8ogk47f7u5z4erp5gpj9al
   expires_at: 2038-01-01 00:00:00
 
+inactive_uninvited_trustedclient:
+  uuid: zzzzz-gj3su-228z32aux8dg2s1
+  api_client: trusted_workbench
+  user: inactive_uninvited
+  api_token: 7s29oj2hzmcmpq80hx9cta0rl5wuf3xfd6r7disusaptz7h9m0
+  expires_at: 2038-01-01 00:00:00
+
 inactive_but_signed_user_agreement:
   uuid: zzzzz-gj3su-247z32aux8dg2s1
   api_client: untrusted
index 8fb800c5f94f8a93bdc2f3990282ea76df7bb51b..8d2586921958570d97104b3fdd8bcefb8e51112f 100644 (file)
@@ -365,3 +365,37 @@ permission_perftest:
       organization: example.com
       role: IT
     getting_started_shown: 2015-03-26 12:34:56.789000000 Z
+
+redirects_to_active:
+  owner_uuid: zzzzz-tpzed-000000000000000
+  uuid: zzzzz-tpzed-1au3is3g3chtthd
+  email: redirects-to-active-user@arvados.local
+  first_name: Active2
+  last_name: User2
+  identity_url: https://redirects-to-active-user.openid.local
+  is_active: true
+  is_admin: false
+  username: redirect_active
+  redirect_to_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
+  prefs:
+    profile:
+      organization: example.com
+      role: Computational biologist
+    getting_started_shown: 2015-03-26 12:34:56.789000000 Z
+
+double_redirects_to_active:
+  owner_uuid: zzzzz-tpzed-000000000000000
+  uuid: zzzzz-tpzed-oiusowoxoz0pk3p
+  email: double-redirects-to-active-user@arvados.local
+  first_name: Active3
+  last_name: User3
+  identity_url: https://double-redirects-to-active-user.openid.local
+  is_active: true
+  is_admin: false
+  username: double_redirect_active
+  redirect_to_user_uuid: zzzzz-tpzed-1au3is3g3chtthd
+  prefs:
+    profile:
+      organization: example.com
+      role: Computational biologist
+    getting_started_shown: 2015-03-26 12:34:56.789000000 Z
index 6f9cf7edcbb6bb13b561cba11cc772c1ff7be097..0497c6a7d56294ae3d0841db5acd8ef9a441d809 100644 (file)
@@ -9,7 +9,7 @@ class UserSessionsApiTest < ActionDispatch::IntegrationTest
     'https://wb.example.com'
   end
 
-  def mock_auth_with(email: nil, username: nil)
+  def mock_auth_with(email: nil, username: nil, identity_url: nil)
     mock = {
       'provider' => 'josh_id',
       'uid' => 'https://edward.example.com',
@@ -22,6 +22,7 @@ class UserSessionsApiTest < ActionDispatch::IntegrationTest
     }
     mock['info']['email'] = email unless email.nil?
     mock['info']['username'] = username unless username.nil?
+    mock['info']['identity_url'] = identity_url unless identity_url.nil?
     post('/auth/josh_id/callback',
          {return_to: client_url},
          {'omniauth.auth' => mock})
@@ -40,6 +41,24 @@ class UserSessionsApiTest < ActionDispatch::IntegrationTest
     assert_equal 'foo', u.username
   end
 
+  test 'existing user login' do
+    mock_auth_with(identity_url: "https://active-user.openid.local")
+    u = assigns(:user)
+    assert_equal 'zzzzz-tpzed-xurymjxw79nv3jz', u.uuid
+  end
+
+  test 'user redirect_to_user_uuid' do
+    mock_auth_with(identity_url: "https://redirects-to-active-user.openid.local")
+    u = assigns(:user)
+    assert_equal 'zzzzz-tpzed-xurymjxw79nv3jz', u.uuid
+  end
+
+  test 'user double redirect_to_user_uuid' do
+    mock_auth_with(identity_url: "https://double-redirects-to-active-user.openid.local")
+    u = assigns(:user)
+    assert_equal 'zzzzz-tpzed-xurymjxw79nv3jz', u.uuid
+  end
+
   test 'create new user during omniauth callback' do
     mock_auth_with(email: 'edward@example.com')
     assert_equal(0, @response.redirect_url.index(client_url),
index 2ecc8726f5e54d91518b3f20c08eb8f1dec41852..c312a532e44f43d63fa65b1d6ff6e7af9028a924 100644 (file)
@@ -1,3 +1,7 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
 package main
 
 import (
index 496fb884d433a5eea350d27818d5e72629e9242f..376d4830b153b85cd82df1220902059b1aa2e4ac 100644 (file)
@@ -1,3 +1,7 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
 package main
 
 import (
index 947033564df01e479d05682617fc041417e5d54f..90235cbf3188d91bc274412ddd5522dc639fa812 100644 (file)
@@ -9,6 +9,7 @@ import (
        "flag"
        "fmt"
        "log"
+       "net/http"
        "os"
        "os/signal"
        "syscall"
@@ -45,6 +46,9 @@ type Config struct {
        // more memory, but can reduce store-and-forward latency when
        // fetching pages)
        CollectionBuffers int
+
+       // Timeout for outgoing http request/response cycle.
+       RequestTimeout arvados.Duration
 }
 
 // RunOptions controls runtime behavior. The flags/options that belong
@@ -107,6 +111,14 @@ func main() {
                log.Fatal(config.DumpAndExit(cfg))
        }
 
+       to := time.Duration(cfg.RequestTimeout)
+       if to == 0 {
+               to = 30 * time.Minute
+       }
+       arvados.DefaultSecureClient.Timeout = to
+       arvados.InsecureHTTPClient.Timeout = to
+       http.DefaultClient.Timeout = to
+
        log.Printf("keep-balance %s started", version)
 
        if *debugFlag {
index 0f4effe6f4e9b7c4e2590cfeb48ef5cd729ec5cd..4c7d5067182fe89783e104c56063fdaf86545c1b 100644 (file)
@@ -19,7 +19,8 @@ KeepServiceTypes:
     - disk
 RunPeriod: 600s
 CollectionBatchSize: 100000
-CollectionBuffers: 1000`)
+CollectionBuffers: 1000
+RequestTimeout: 30m`)
 
 func usage() {
        fmt.Fprintf(os.Stderr, `
@@ -86,6 +87,11 @@ Tuning resource usage:
     while the current page is still being processed. If this is zero
     or omitted, pages are processed serially.
 
+    RequestTimeout is the maximum time keep-balance will spend on a
+    single HTTP request (getting a page of collections, getting the
+    block index from a keepstore server, or sending a trash or pull
+    list to a keepstore server). Defaults to 30 minutes.
+
 Limitations:
 
     keep-balance does not attempt to discover whether committed pull
index 52db776a4319bf34846c23dfed0a8f8b63fe757b..473171e1f5c41c0dd371844e4432c46d588e29ab 100644 (file)
@@ -1,3 +1,7 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
 package main
 
 import "golang.org/x/net/webdav"
index fea9cf6e8beeda2dcec9f15f88d759303286f173..35a8b156dec7cb650015b9e22155a16a18177615 100755 (executable)
@@ -1,4 +1,8 @@
 #!/bin/sh
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
 # system one time tasks
 
 PATH=/command:/sbin:/bin:/usr/sbin:/usr/bin
index 6b092eae678f0b8f825331b9140842d3bb7a8639..5812f3d8b0cea307b793016156b8fa73b3909224 100755 (executable)
@@ -1,4 +1,7 @@
 #!/bin/sh
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
 
 PATH=/usr/local/bin:/usr/local/sbin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/X11R6/bin
 
index 525b96bbb667564ecf7f2eef66d37009796a7406..242c035f66dcbb6d865d52bc421ec91881a093fb 100755 (executable)
@@ -1,4 +1,8 @@
 #!/bin/sh
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
 exec 2>&1
 
 PATH=/command:/sbin:/bin:/usr/sbin:/usr/bin
index 02bb2ea91884d110c8ad1add1cf7cab87ce30304..d4d2190b152d284c55bc5b4bdd988787be2d7f69 100755 (executable)
@@ -1,4 +1,8 @@
 #!/bin/sh
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
 exec 2>&1
 
 PATH=/command:/sbin:/bin:/usr/sbin:/usr/bin
index 78c8d4278f32b2050ff006d168cf61213804645a..562ee839e0832c9b6d394c00f62c9a524f89d30a 100644 (file)
@@ -1,3 +1,7 @@
+// Copyright (c) 2009 Dan Vanderkam. All rights reserved.
+//
+// SPDX-License-Identifier: MIT
+
 /**
  * Synchronize zooming and/or selections between a set of dygraphs.
  *
@@ -31,7 +35,6 @@
  * You may also set `range: false` if you wish to only sync the x-axis.
  * The `range` option has no effect unless `zoom` is true (the default).
  *
- * SPDX-License-Identifier: MIT
  * Original source: https://github.com/danvk/dygraphs/blob/master/src/extras/synchronizer.js
  * at commit b55a71d768d2f8de62877c32b3aec9e9975ac389
  *