Merge branch 'master' into 2051-nondeterministic-jobs
authorPeter Amstutz <peter.amstutz@curoverse.com>
Tue, 25 Mar 2014 18:40:01 +0000 (14:40 -0400)
committerPeter Amstutz <peter.amstutz@curoverse.com>
Tue, 25 Mar 2014 18:40:01 +0000 (14:40 -0400)
26 files changed:
apps/workbench/Gemfile
apps/workbench/Gemfile.lock
apps/workbench/README.rdoc [deleted file]
apps/workbench/README.textile [new file with mode: 0644]
apps/workbench/app/assets/javascripts/editable.js
apps/workbench/app/controllers/api_client_authorizations_controller.rb
apps/workbench/app/controllers/pipeline_templates_controller.rb
apps/workbench/app/models/arvados_api_client.rb
apps/workbench/app/models/arvados_base.rb
apps/workbench/app/models/arvados_resource_list.rb
apps/workbench/app/views/application/_paging.html.erb
apps/workbench/app/views/layouts/application.html.erb
apps/workbench/config/application.default.yml
apps/workbench/config/environments/development.rb.example
apps/workbench/config/environments/production.rb.example
apps/workbench/config/environments/test.rb.example
apps/workbench/test/integration/api_client_authorizations_test.rb [new file with mode: 0644]
apps/workbench/test/integration/logins_test.rb [new file with mode: 0644]
apps/workbench/test/integration/virtual_machines_test.rb [new file with mode: 0644]
apps/workbench/test/integration_helper.rb [new file with mode: 0644]
doc/install/install-workbench-app.html.textile.liquid
doc/user/tutorials/tutorial-keep.html.textile.liquid
sdk/cli/bin/crunch-job
services/api/script/crunch-dispatch.rb
services/api/script/rails
services/api/test/integration/permissions_test.rb [new file with mode: 0644]

index 6ae12f75db306278fcb1227f4b711a962f66b84f..b273d9191c875417a9edd5cf90f8ffdf305a007a 100644 (file)
@@ -23,6 +23,13 @@ group :assets do
   gem 'uglifier', '>= 1.0.3'
 end
 
+group :test do
+  gem 'rvm-capistrano'
+  gem 'selenium-webdriver'
+  gem 'capybara'
+  gem 'poltergeist'
+end
+
 gem 'jquery-rails'
 gem 'bootstrap-sass', '~> 3.1.0'
 gem 'bootstrap-x-editable-rails'
@@ -45,8 +52,6 @@ gem 'less-rails'
 # To use debugger
 #gem 'byebug'
 
-gem 'rvm-capistrano', :group => :test
-
 gem 'passenger', :group => :production
 gem 'andand'
 gem 'RedCloth'
index c7ffeb00574af4be2d89aa22589e555bd02e75ff..0c65ca80ffb3d764d6446d4ce714e551a90eb3b1 100644 (file)
@@ -42,6 +42,15 @@ GEM
       net-sftp (>= 2.0.0)
       net-ssh (>= 2.0.14)
       net-ssh-gateway (>= 1.1.0)
+    capybara (2.2.1)
+      mime-types (>= 1.16)
+      nokogiri (>= 1.3.3)
+      rack (>= 1.0.0)
+      rack-test (>= 0.5.4)
+      xpath (~> 2.0)
+    childprocess (0.5.1)
+      ffi (~> 1.0, >= 1.0.11)
+    cliver (0.3.2)
     coffee-rails (3.2.2)
       coffee-script (>= 2.2.0)
       railties (~> 3.2.0)
@@ -54,6 +63,7 @@ GEM
     deep_merge (1.0.1)
     erubis (2.7.0)
     execjs (2.0.2)
+    ffi (1.9.3)
     highline (1.6.20)
     hike (1.2.3)
     httpclient (2.3.4.1)
@@ -73,6 +83,7 @@ GEM
       mime-types (~> 1.16)
       treetop (~> 1.4.8)
     mime-types (1.25)
+    mini_portile (0.5.2)
     multi_json (1.8.2)
     net-scp (1.1.2)
       net-ssh (>= 2.6.5)
@@ -81,6 +92,8 @@ GEM
     net-ssh (2.7.0)
     net-ssh-gateway (1.2.0)
       net-ssh (>= 2.6.5)
+    nokogiri (1.6.1)
+      mini_portile (~> 0.5.0)
     oj (2.1.7)
     passenger (4.0.23)
       daemon_controller (>= 1.1.0)
@@ -90,6 +103,11 @@ GEM
       actionpack
       activesupport
       rails (>= 3.0.0)
+    poltergeist (1.5.0)
+      capybara (~> 2.1)
+      cliver (~> 0.3.1)
+      multi_json (~> 1.0)
+      websocket-driver (>= 0.2.0)
     polyglot (0.3.3)
     rack (1.4.5)
     rack-cache (1.2)
@@ -117,6 +135,7 @@ GEM
     rdoc (3.12.2)
       json (~> 1.4)
     ref (1.0.5)
+    rubyzip (1.1.0)
     rvm-capistrano (1.5.1)
       capistrano (~> 2.15.4)
     sass (3.2.12)
@@ -124,6 +143,11 @@ GEM
       railties (~> 3.2.0)
       sass (>= 3.1.10)
       tilt (~> 1.3)
+    selenium-webdriver (2.40.0)
+      childprocess (>= 0.5.0)
+      multi_json (~> 1.0)
+      rubyzip (~> 1.0)
+      websocket (~> 1.0.4)
     sprockets (2.2.2)
       hike (~> 1.2)
       multi_json (~> 1.0)
@@ -144,6 +168,10 @@ GEM
     uglifier (2.3.1)
       execjs (>= 0.3.0)
       json (>= 1.8.0)
+    websocket (1.0.7)
+    websocket-driver (0.3.2)
+    xpath (2.0.0)
+      nokogiri (~> 1.3)
 
 PLATFORMS
   ruby
@@ -153,6 +181,7 @@ DEPENDENCIES
   andand
   bootstrap-sass (~> 3.1.0)
   bootstrap-x-editable-rails
+  capybara
   coffee-rails (~> 3.2.0)
   deep_merge
   httpclient
@@ -163,10 +192,12 @@ DEPENDENCIES
   oj
   passenger
   piwik_analytics
+  poltergeist
   rails (~> 3.2.0)
   rvm-capistrano
   sass
   sass-rails (~> 3.2.0)
+  selenium-webdriver
   sqlite3
   themes_for_rails
   therubyracer
diff --git a/apps/workbench/README.rdoc b/apps/workbench/README.rdoc
deleted file mode 100644 (file)
index 7c36f23..0000000
+++ /dev/null
@@ -1,261 +0,0 @@
-== Welcome to Rails
-
-Rails is a web-application framework that includes everything needed to create
-database-backed web applications according to the Model-View-Control pattern.
-
-This pattern splits the view (also called the presentation) into "dumb"
-templates that are primarily responsible for inserting pre-built data in between
-HTML tags. The model contains the "smart" domain objects (such as Account,
-Product, Person, Post) that holds all the business logic and knows how to
-persist themselves to a database. The controller handles the incoming requests
-(such as Save New Account, Update Product, Show Post) by manipulating the model
-and directing data to the view.
-
-In Rails, the model is handled by what's called an object-relational mapping
-layer entitled Active Record. This layer allows you to present the data from
-database rows as objects and embellish these data objects with business logic
-methods. You can read more about Active Record in
-link:files/vendor/rails/activerecord/README.html.
-
-The controller and view are handled by the Action Pack, which handles both
-layers by its two parts: Action View and Action Controller. These two layers
-are bundled in a single package due to their heavy interdependence. This is
-unlike the relationship between the Active Record and Action Pack that is much
-more separate. Each of these packages can be used independently outside of
-Rails. You can read more about Action Pack in
-link:files/vendor/rails/actionpack/README.html.
-
-
-== Getting Started
-
-1. At the command prompt, create a new Rails application:
-       <tt>rails new myapp</tt> (where <tt>myapp</tt> is the application name)
-
-2. Change directory to <tt>myapp</tt> and start the web server:
-       <tt>cd myapp; rails server</tt> (run with --help for options)
-
-3. Go to http://localhost:3000/ and you'll see:
-       "Welcome aboard: You're riding Ruby on Rails!"
-
-4. Follow the guidelines to start developing your application. You can find
-the following resources handy:
-
-* The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html
-* Ruby on Rails Tutorial Book: http://www.railstutorial.org/
-
-
-== Debugging Rails
-
-Sometimes your application goes wrong. Fortunately there are a lot of tools that
-will help you debug it and get it back on the rails.
-
-First area to check is the application log files. Have "tail -f" commands
-running on the server.log and development.log. Rails will automatically display
-debugging and runtime information to these files. Debugging info will also be
-shown in the browser on requests from 127.0.0.1.
-
-You can also log your own messages directly into the log file from your code
-using the Ruby logger class from inside your controllers. Example:
-
-  class WeblogController < ActionController::Base
-    def destroy
-      @weblog = Weblog.find(params[:id])
-      @weblog.destroy
-      logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!")
-    end
-  end
-
-The result will be a message in your log file along the lines of:
-
-  Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1!
-
-More information on how to use the logger is at http://www.ruby-doc.org/core/
-
-Also, Ruby documentation can be found at http://www.ruby-lang.org/. There are
-several books available online as well:
-
-* Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe)
-* Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide)
-
-These two books will bring you up to speed on the Ruby language and also on
-programming in general.
-
-
-== Debugger
-
-Debugger support is available through the debugger command when you start your
-Mongrel or WEBrick server with --debugger. This means that you can break out of
-execution at any point in the code, investigate and change the model, and then,
-resume execution! You need to install ruby-debug to run the server in debugging
-mode. With gems, use <tt>sudo gem install ruby-debug</tt>. Example:
-
-  class WeblogController < ActionController::Base
-    def index
-      @posts = Post.all
-      debugger
-    end
-  end
-
-So the controller will accept the action, run the first line, then present you
-with a IRB prompt in the server window. Here you can do things like:
-
-  >> @posts.inspect
-  => "[#<Post:0x14a6be8
-          @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}>,
-       #<Post:0x14a6620
-          @attributes={"title"=>"Rails", "body"=>"Only ten..", "id"=>"2"}>]"
-  >> @posts.first.title = "hello from a debugger"
-  => "hello from a debugger"
-
-...and even better, you can examine how your runtime objects actually work:
-
-  >> f = @posts.first
-  => #<Post:0x13630c4 @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}>
-  >> f.
-  Display all 152 possibilities? (y or n)
-
-Finally, when you're ready to resume execution, you can enter "cont".
-
-
-== Console
-
-The console is a Ruby shell, which allows you to interact with your
-application's domain model. Here you'll have all parts of the application
-configured, just like it is when the application is running. You can inspect
-domain models, change values, and save to the database. Starting the script
-without arguments will launch it in the development environment.
-
-To start the console, run <tt>rails console</tt> from the application
-directory.
-
-Options:
-
-* Passing the <tt>-s, --sandbox</tt> argument will rollback any modifications
-  made to the database.
-* Passing an environment name as an argument will load the corresponding
-  environment. Example: <tt>rails console production</tt>.
-
-To reload your controllers and models after launching the console run
-<tt>reload!</tt>
-
-More information about irb can be found at:
-link:http://www.rubycentral.org/pickaxe/irb.html
-
-
-== dbconsole
-
-You can go to the command line of your database directly through <tt>rails
-dbconsole</tt>. You would be connected to the database with the credentials
-defined in database.yml. Starting the script without arguments will connect you
-to the development database. Passing an argument will connect you to a different
-database, like <tt>rails dbconsole production</tt>. Currently works for MySQL,
-PostgreSQL and SQLite 3.
-
-== Description of Contents
-
-The default directory structure of a generated Ruby on Rails application:
-
-  |-- app
-  |   |-- assets
-  |       |-- images
-  |       |-- javascripts
-  |       `-- stylesheets
-  |   |-- controllers
-  |   |-- helpers
-  |   |-- mailers
-  |   |-- models
-  |   `-- views
-  |       `-- layouts
-  |-- config
-  |   |-- environments
-  |   |-- initializers
-  |   `-- locales
-  |-- db
-  |-- doc
-  |-- lib
-  |   `-- tasks
-  |-- log
-  |-- public
-  |-- script
-  |-- test
-  |   |-- fixtures
-  |   |-- functional
-  |   |-- integration
-  |   |-- performance
-  |   `-- unit
-  |-- tmp
-  |   |-- cache
-  |   |-- pids
-  |   |-- sessions
-  |   `-- sockets
-  `-- vendor
-      |-- assets
-          `-- stylesheets
-      `-- plugins
-
-app
-  Holds all the code that's specific to this particular application.
-
-app/assets
-  Contains subdirectories for images, stylesheets, and JavaScript files.
-
-app/controllers
-  Holds controllers that should be named like weblogs_controller.rb for
-  automated URL mapping. All controllers should descend from
-  ApplicationController which itself descends from ActionController::Base.
-
-app/models
-  Holds models that should be named like post.rb. Models descend from
-  ActiveRecord::Base by default.
-
-app/views
-  Holds the template files for the view that should be named like
-  weblogs/index.html.erb for the WeblogsController#index action. All views use
-  eRuby syntax by default.
-
-app/views/layouts
-  Holds the template files for layouts to be used with views. This models the
-  common header/footer method of wrapping views. In your views, define a layout
-  using the <tt>layout :default</tt> and create a file named default.html.erb.
-  Inside default.html.erb, call <% yield %> to render the view using this
-  layout.
-
-app/helpers
-  Holds view helpers that should be named like weblogs_helper.rb. These are
-  generated for you automatically when using generators for controllers.
-  Helpers can be used to wrap functionality for your views into methods.
-
-config
-  Configuration files for the Rails environment, the routing map, the database,
-  and other dependencies.
-
-db
-  Contains the database schema in schema.rb. db/migrate contains all the
-  sequence of Migrations for your schema.
-
-doc
-  This directory is where your application documentation will be stored when
-  generated using <tt>rake doc:app</tt>
-
-lib
-  Application specific libraries. Basically, any kind of custom code that
-  doesn't belong under controllers, models, or helpers. This directory is in
-  the load path.
-
-public
-  The directory available for the web server. Also contains the dispatchers and the
-  default HTML files. This should be set as the DOCUMENT_ROOT of your web
-  server.
-
-script
-  Helper scripts for automation and generation.
-
-test
-  Unit and functional tests along with fixtures. When using the rails generate
-  command, template test files will be generated for you and placed in this
-  directory.
-
-vendor
-  External libraries that the application depends on. Also includes the plugins
-  subdirectory. If the app has frozen rails, those gems also go here, under
-  vendor/rails/. This directory is in the load path.
diff --git a/apps/workbench/README.textile b/apps/workbench/README.textile
new file mode 100644 (file)
index 0000000..e249b13
--- /dev/null
@@ -0,0 +1,17 @@
+h1. Developing Workbench
+
+This document includes information to help developers who would like to contribute to Workbench.  If you just want to install it, please refer to our "Workbench installation guide":http://doc.arvados.org/install/install-workbench-app.html.
+
+h2. Running tests
+
+The Workbench application includes a series of integration tests.  When you run these, it starts the API server in a test environment, with all of its fixtures loaded, then tests Workbench by starting that server and making requests against it.
+
+In addition to bundled gems, running the integration tests requires "PhantomJS":http://phantomjs.org/download.html to test JavaScript elements.  The simplest way to get started is to download one of the binary builds provided, and install the executable into one of the directories in your @$PATH@.
+
+h2. Writing tests
+
+Integration tests are written with Capybara, which drives a fully-featured Web browser to interact with Workbench exactly as a user would.
+
+If your test requires JavaScript support, your test method should start with the line @Capybara.current_driver = Capybara.javascript_driver@.  Otherwise, Capybara defaults to a simpler browser for speed.
+
+In most tests, you can directly call "Capybara's Session methods":http://rubydoc.info/github/jnicklas/capybara/Capybara/Session to drive the browser and check its state.  If you need finer-grained control, refer to the "full Capybara documentation":http://rubydoc.info/github/jnicklas/capybara/Capybara.
index e37b9444c1615bfd10ce760fa3934f0ab7e76c0d..e6799bf78b40d4a3b3ef0cb4cbcb3c764db82d4d 100644 (file)
@@ -23,3 +23,12 @@ $.fn.editable.defaults.validate = function (value) {
         return "Invalid selection";
     }
 }
+
+$.fn.editabletypes.text.defaults.tpl = '<input type="text" name="editable-text">'
+
+$.fn.editableform.buttons = '\
+<button type="submit" class="btn btn-primary btn-sm editable-submit" \
+  id="editable-submit"><i class="glyphicon glyphicon-ok"></i></button>\
+<button type="button" class="btn btn-default btn-sm editable-cancel" \
+  id="editable-cancel"><i class="glyphicon glyphicon-remove"></i></button>\
+'
index 81e324a46a6b379e5ec06583410d11ec69fcf5bd..24b4ae3185d4701c7152af5a8f582f2e97ecb424 100644 (file)
@@ -1,8 +1,15 @@
 class ApiClientAuthorizationsController < ApplicationController
   def index
-    @objects = model_class.all.to_ary.reject do |x|
+    m = model_class.all
+    items_available = m.items_available
+    offset = m.result_offset
+    limit = m.result_limit
+    filtered = m.to_ary.reject do |x|
       x.api_client_id == 0 or (x.expires_at and x.expires_at < Time.now) rescue false
     end
+    ArvadosApiClient::patch_paging_vars(filtered, items_available, offset, limit)
+    @objects = ArvadosResourceList.new(ApiClientAuthorization)
+    @objects.results= filtered
     super
   end
 
index 98101b5e9b0953b61060a554d54456d8c37b7841..5173d4e376b9a2ae244ca90d05b3130f3e2e06d7 100644 (file)
@@ -1,10 +1,7 @@
 class PipelineTemplatesController < ApplicationController
   
   def show
-    @objects = [] 
-    PipelineInstance.where(pipeline_template_uuid: @object.uuid).each do |pipeline|
-      @objects.push(pipeline)
-    end
+    @objects = PipelineInstance.where(pipeline_template_uuid: @object.uuid)
     super
   end
 
index 6d9269e667a8b3e3b169b1f783db7c85d0539174..2f83a411522b736e216d7e4af04962ae9b1e4d05 100644 (file)
@@ -90,22 +90,26 @@ class ArvadosApiClient
     resp
   end
 
+  def self.patch_paging_vars(ary, items_available, offset, limit)
+    if items_available
+      (class << ary; self; end).class_eval { attr_accessor :items_available }
+      ary.items_available = items_available
+    end
+    if offset
+      (class << ary; self; end).class_eval { attr_accessor :offset }
+      ary.offset = offset
+    end
+    if limit
+      (class << ary; self; end).class_eval { attr_accessor :limit }
+      ary.limit = limit
+    end    
+    ary
+  end
+
   def unpack_api_response(j, kind=nil)
     if j.is_a? Hash and j[:items].is_a? Array and j[:kind].match(/(_list|List)$/)
       ary = j[:items].collect { |x| unpack_api_response x, j[:kind] }
-      if j[:items_available]
-        (class << ary; self; end).class_eval { attr_accessor :items_available }
-        ary.items_available = j[:items_available]
-      end
-      if j[:offset]
-        (class << ary; self; end).class_eval { attr_accessor :offset }
-        ary.offset = j[:offset]
-      end
-      if j[:limit]
-        (class << ary; self; end).class_eval { attr_accessor :limit }
-        ary.limit = j[:limit]
-      end
-      ary
+      ArvadosApiClient::patch_paging_vars(ary, j[:items_available], j[:offset], j[:limit])
     elsif j.is_a? Hash and (kind || j[:kind])
       oclass = self.kind_class(kind || j[:kind])
       if oclass
@@ -145,10 +149,6 @@ class ArvadosApiClient
     Rails.configuration.arvados_v1_base
   end
 
-  def arvados_schema
-    @arvados_schema ||= api 'schema', ''
-  end
-
   def discovery
     @discovery ||= api '../../discovery/v1/apis/arvados/v1/rest', ''
   end
index fbf7ee5e799e48a07c831d553733b8c56bca9855..43f40d9f960f3498bab54282a2365629be3e89da 100644 (file)
@@ -45,20 +45,25 @@ class ArvadosBase < ActiveRecord::Base
     return @columns unless @columns.nil?
     @columns = []
     @attribute_info ||= {}
-    return @columns if $arvados_api_client.arvados_schema[self.to_s.to_sym].nil?
-    $arvados_api_client.arvados_schema[self.to_s.to_sym].each do |coldef|
-      k = coldef[:name].to_sym
-      if coldef[:type] == coldef[:type].downcase
-        @columns << column(k, coldef[:type].to_sym)
+    schema = $arvados_api_client.discovery[:schemas][self.to_s.to_sym]
+    return @columns if schema.nil?
+    schema[:properties].each do |k, coldef|
+      case k
+      when :etag, :kind
+        attr_reader k
       else
-        @columns << column(k, :text)
-        serialize k, coldef[:type].constantize
+        if coldef[:type] == coldef[:type].downcase
+          # boolean, integer, etc.
+          @columns << column(k, coldef[:type].to_sym)
+        else
+          # Hash, Array
+          @columns << column(k, :text)
+          serialize k, coldef[:type].constantize
+        end
+        attr_accessible k
+        @attribute_info[k] = coldef
       end
-      attr_accessible k
-      @attribute_info[k] = coldef
     end
-    attr_reader :etag
-    attr_reader :kind
     @columns
   end
 
index 3842c97c373d67343017e5cbc6aff59aa2eda4e0..6b17ececab17c8248c3e7a60e7a7cecf41109da9 100644 (file)
@@ -68,6 +68,10 @@ class ArvadosResourceList
     @results
   end
 
+  def results=(r)
+    @results = r
+  end
+
   def all
     where({})
   end
index 463e65a81487a31fdd7e2fd8b0c5f9c26e523af7..df9d08d778c997fb641600eecb70c87fddb59ed1 100644 (file)
@@ -11,7 +11,13 @@ min-width: 1.2em;
 }
 <% end %>
 
-<% if results.result_offset != nil and results.result_limit != nil and results.items_available != nil %>
+<% if results.respond_to? :result_offset and
+       results.respond_to? :result_limit and
+       results.respond_to? :items_available and
+       results.result_offset != nil and
+       results.result_limit != nil and
+       results.items_available != nil 
+%>
 <div class="index-paging">
   Displaying <%= results.result_offset+1 %> &ndash; 
   <%= if results.result_offset + results.result_limit > results.items_available 
index abef47136fd21dd90ae67cf246b51431344ef7b8..b5c75fc64f9020b7e8faa624c8b0de5b7e74ea36 100644 (file)
         <% end %>
 
         <li class="dropdown">
-          <a href="#" class="dropdown-toggle" data-toggle="dropdown">
+          <a href="#" class="dropdown-toggle" data-toggle="dropdown" id="user-menu">
             <span class="glyphicon glyphicon-user"></span><span class="caret"></span>
           </a>
           <ul class="dropdown-menu" role="menu">
index 139255214f49c278d786ffe93e018a1c6d6686e7..bbed01ea315b064d1cd2ea2db49b1e433fb32967 100644 (file)
@@ -1,4 +1,4 @@
-# Do not use this file for site configuration. Create config.yml
+# Do not use this file for site configuration. Create application.yml
 # instead (see application.yml.example).
 
 development:
@@ -14,7 +14,7 @@ development:
   assets.compress: false
   assets.debug: true
   profiling_enabled: true
-  site_name: Workbench:dev
+  site_name: Arvados Workbench (dev)
 
 production:
   force_ssl: true
@@ -51,6 +51,14 @@ test:
   profiling_enabled: false
   secret_token: <%= rand(2**256).to_s(36) %>
 
+  # When you run the Workbench's integration tests, it starts the API
+  # server as a dependency.  These settings should match the API
+  # server's Rails defaults.  If you adjust those, change these
+  # settings in application.yml to match.
+  arvados_login_base: https://localhost:3001/login
+  arvados_v1_base: https://localhost:3001/arvados/v1
+  arvados_insecure_https: true
+
   site_name: Workbench:test
 
 common:
index ba3dbbe30d868cea5778ec498d243d6abdbe3ed1..389a25420f1b31eb75290dec3d435acf551e62f7 100644 (file)
@@ -35,24 +35,4 @@ ArvadosWorkbench::Application.configure do
   # Expands the lines which load the assets
   config.assets.debug = true
 
-  # Log timing data for API transactions
-  config.profiling_enabled = true
-
-  config.arvados_login_base = 'http://arvados.local/login'
-  config.arvados_v1_base = 'http://arvados.local/arvados/v1'
-  config.arvados_insecure_https = true # true = do not check server certificate
-
-  config.data_import_dir = '/tmp/arvados-workbench-upload'
-  config.data_export_dir = '/tmp/arvados-workbench-download'
-
-  config.secret_token = File.read('config/.secret_token') if File.exist? 'config/.secret_token'
-
-  config.site_name = 'Arvados Workbench (dev)'
-  config.activation_contact_link = 'mailto:info@arvados.org'
-
-  config.arvados_docsite = 'http://doc.arvados.org'
-
-  config.arvados_theme = 'default'
-
-  config.show_user_agreement_inline = false
 end
index 2b4a39f963cd9039a9f302ba124349bdaa972f0e..bb7595454e381abd82ff3f740802274320882503 100644 (file)
@@ -68,23 +68,4 @@ ArvadosWorkbench::Application.configure do
   # Log timing data for API transactions
   config.profiling_enabled = false
 
-  config.arvados_login_base = 'https://arvados.local/login'
-  config.arvados_v1_base = 'https://arvados.local/arvados/v1'
-  config.arvados_insecure_https = false # true = do not check server certificate
-
-  config.data_import_dir = '/data/arvados-workbench-upload/data'
-  config.data_export_dir = '/data/arvados-workbench-download/data'
-
-  # Authentication stub: hard code pre-approved API tokens.
-  # config.accept_api_token = { rand(2**256).to_s(36) => true }
-  config.accept_api_token = {}
-
-  config.site_name = 'Arvados Workbench'
-  config.activation_contact_link = 'mailto:info@arvados.org'
-
-  config.arvados_docsite = 'http://doc.arvados.org'
-
-  config.arvados_theme = 'default'
-
-  config.show_user_agreement_inline = false
 end
index c4a5d229169200382950a33228eab3cec7cc70a2..b3cb72aff258b8d9b5946cc1cd9fa1865cfe4cd1 100644 (file)
@@ -38,23 +38,4 @@ ArvadosWorkbench::Application.configure do
   # Log timing data for API transactions
   config.profiling_enabled = false
 
-  config.arvados_login_base = 'http://arvados.local/login'
-  config.arvados_v1_base = 'https://arvados.local/arvados/v1'
-  config.arvados_insecure_https = true # true = do not check server certificate
-
-  config.data_import_dir = '/data/arvados-workbench-upload'
-  config.data_export_dir = '/data/arvados-workbench-download'
-
-  # Authentication stub: hard code pre-approved API tokens.
-  # config.accept_api_token = { rand(2**256).to_s(36) => true }
-  config.accept_api_token = {}
-
-  config.site_name = 'Arvados Workbench (test)'
-  config.activation_contact_link = 'mailto:info@arvados.org'
-
-  config.arvados_docsite = 'http://doc.arvados.org'
-
-  config.arvados_theme = 'default'
-
-  config.show_user_agreement_inline = false
 end
diff --git a/apps/workbench/test/integration/api_client_authorizations_test.rb b/apps/workbench/test/integration/api_client_authorizations_test.rb
new file mode 100644 (file)
index 0000000..6312034
--- /dev/null
@@ -0,0 +1,11 @@
+require 'integration_helper'
+
+class ApiClientAuthorizationsTest < ActionDispatch::IntegrationTest
+  test "try loading Manage API tokens page" do
+    Capybara.current_driver = Capybara.javascript_driver
+    visit page_with_token('admin_trustedclient')
+    click_link 'user-menu'
+    click_link 'Manage API tokens'
+    assert_equal 200, status_code
+  end
+end
diff --git a/apps/workbench/test/integration/logins_test.rb b/apps/workbench/test/integration/logins_test.rb
new file mode 100644 (file)
index 0000000..84170dd
--- /dev/null
@@ -0,0 +1,14 @@
+require 'test_helper'
+
+class LoginsTest < ActionDispatch::IntegrationTest
+  test "login with api_token works after redirect" do
+    visit page_with_token('active_trustedclient')
+    assert page.has_text? 'Recent jobs'
+    assert_no_match(/\bapi_token=/, current_path)
+  end
+
+  test "can't use expired token" do
+    visit page_with_token('expired_trustedclient')
+    assert page.has_text? 'Log in'
+  end
+end
diff --git a/apps/workbench/test/integration/virtual_machines_test.rb b/apps/workbench/test/integration/virtual_machines_test.rb
new file mode 100644 (file)
index 0000000..541a7aa
--- /dev/null
@@ -0,0 +1,17 @@
+require 'integration_helper'
+
+class VirtualMachinesTest < ActionDispatch::IntegrationTest
+  test "make and name a new virtual machine" do
+    Capybara.current_driver = Capybara.javascript_driver
+    visit page_with_token('admin_trustedclient')
+    click_link 'Virtual machines'
+    assert page.has_text? 'testvm.shell'
+    click_on 'Add a new virtual machine'
+    assert page.has_text? 'none'
+    click_link 'none'
+    assert page.has_text? 'Update hostname'
+    fill_in 'editable-text', with: 'testname'
+    click_button 'editable-submit'
+    assert page.has_text? 'testname'
+  end
+end
diff --git a/apps/workbench/test/integration_helper.rb b/apps/workbench/test/integration_helper.rb
new file mode 100644 (file)
index 0000000..8e317ba
--- /dev/null
@@ -0,0 +1,68 @@
+require 'test_helper'
+require 'capybara/rails'
+require 'capybara/poltergeist'
+require 'uri'
+require 'yaml'
+
+$ARV_API_SERVER_DIR = File.expand_path('../../../../services/api', __FILE__)
+SERVER_PID_PATH = 'tmp/pids/server.pid'
+
+class ActionDispatch::IntegrationTest
+  # Make the Capybara DSL available in all integration tests
+  include Capybara::DSL
+
+  def self.api_fixture(name)
+    # Returns the data structure from the named API server test fixture.
+    path = File.join($ARV_API_SERVER_DIR, 'test', 'fixtures', "#{name}.yml")
+    YAML.load(IO.read(path))
+  end
+
+  @@API_AUTHS = api_fixture('api_client_authorizations')
+
+  def page_with_token(token, path='/')
+    # Generate a page path with an embedded API token.
+    # Typical usage: visit page_with_token('token_name', page)
+    # The token can be specified by the name of an api_client_authorizations
+    # fixture, or passed as a raw string.
+    api_token = ((@@API_AUTHS.include? token) ?
+                 @@API_AUTHS[token]['api_token'] : token)
+    sep = (path.include? '?') ? '&' : '?'
+    q_string = URI.encode_www_form('api_token' => api_token)
+    "#{path}#{sep}#{q_string}"
+  end
+end
+
+class IntegrationTestRunner < MiniTest::Unit
+  # Make a hash that unsets Bundle's environment variables.
+  # We'll use this environment when we launch Bundle commands in the API
+  # server.  Otherwise, those commands will try to use Workbench's gems, etc.
+  @@APIENV = ENV.map { |(key, val)| (key =~ /^BUNDLE_/) ? [key, nil] : nil }.
+    compact.to_h
+
+  def _system(*cmd)
+    if not system(@@APIENV, *cmd)
+      raise RuntimeError, "#{cmd[0]} returned exit code #{$?.exitstatus}"
+    end
+  end
+
+  def _run(args=[])
+    Capybara.javascript_driver = :poltergeist
+    server_pid = Dir.chdir($ARV_API_SERVER_DIR) do |apidir|
+      _system('bundle', 'exec', 'rake', 'db:test:load')
+      _system('bundle', 'exec', 'rake', 'db:fixtures:load')
+      _system('bundle', 'exec', 'rails', 'server', '-d')
+      timeout = Time.now.tv_sec + 5
+      while (not File.exists? SERVER_PID_PATH) and (Time.now.tv_sec < timeout)
+        sleep 0.2
+      end
+      IO.read(SERVER_PID_PATH).to_i
+    end
+    begin
+      super(args)
+    ensure
+      Process.kill('TERM', server_pid)
+    end
+  end
+end
+
+MiniTest::Unit.runner = IntegrationTestRunner.new
index 73fbd15c944878436c098b350da97808447da4cc..be6a04de21ea2bc60ffc327f59742eba37e59f7b 100644 (file)
@@ -50,5 +50,3 @@ irb(main):001:0&gt; <span class="userinput">ApiClient.where('url_prefix like ?',
 irb(main):002:0&gt; <span class="userinput">ApiClient.find(1234).update_attributes is_trusted: true</span>
 </code></pre>
 </notextile>
-
-
index 6a797c001ad15f107290bef1adbd804dad58ea51..5a5e8796cbb67352256f9a06f24839f5d55281e2 100644 (file)
@@ -112,7 +112,7 @@ Use @arv keep get@ to download the contents of a collection and place it in the
 </code></pre>
 </notextile>
 
-You can also download indvidual files:
+You can also download individual files:
 
 <notextile>
 <pre><code>/scratch/<b>you</b>$ <span class="userinput">arv keep get 887cd41e9c613463eab2f0d885c6dd96+83/alice.txt .</span>
index 4c8acbb59a5ad917dd1f3d43203bb203fe84f4f3..c09826848ef746a2ca9c8dc1ef646533deb5207c 100755 (executable)
@@ -33,6 +33,12 @@ Path to .git directory where the specified commit is found.
 
 Arvados API authorization token to use during the course of the job.
 
+=item --no-clear-tmp
+
+Do not clear per-job/task temporary directories during initial job
+setup. This can speed up development and debugging when running jobs
+locally.
+
 =back
 
 =head1 RUNNING JOBS LOCALLY
@@ -74,6 +80,7 @@ use Getopt::Long;
 use Warehouse;
 use Warehouse::Stream;
 use IPC::System::Simple qw(capturex);
+use Fcntl ':flock';
 
 $ENV{"TMPDIR"} ||= "/tmp";
 unless (defined $ENV{"CRUNCH_TMP"}) {
@@ -92,11 +99,13 @@ my $force_unlock;
 my $git_dir;
 my $jobspec;
 my $job_api_token;
+my $no_clear_tmp;
 my $resume_stash;
 GetOptions('force-unlock' => \$force_unlock,
            'git-dir=s' => \$git_dir,
            'job=s' => \$jobspec,
            'job-api-token=s' => \$job_api_token,
+           'no-clear-tmp' => \$no_clear_tmp,
            'resume-stash=s' => \$resume_stash,
     );
 
@@ -323,6 +332,12 @@ else
 }
 
 
+if (!$have_slurm)
+{
+  must_lock_now("$ENV{CRUNCH_TMP}/.lock", "a job is already running here.");
+}
+
+
 my $build_script;
 
 
@@ -331,6 +346,11 @@ $ENV{"CRUNCH_SRC_COMMIT"} = $Job->{script_version};
 my $skip_install = ($local_job && $Job->{script_version} =~ m{^/});
 if ($skip_install)
 {
+  if (!defined $no_clear_tmp) {
+    my $clear_tmp_cmd = 'rm -rf $JOB_WORK $CRUNCH_TMP/opt $CRUNCH_TMP/src*';
+    system($clear_tmp_cmd) == 0
+       or croak ("`$clear_tmp_cmd` failed: ".($?>>8));
+  }
   $ENV{"CRUNCH_SRC"} = $Job->{script_version};
   for my $src_path ("$ENV{CRUNCH_SRC}/arvados/sdk/python") {
     if (-d $src_path) {
@@ -351,22 +371,24 @@ else
   Log (undef, "Install revision ".$Job->{script_version});
   my $nodelist = join(",", @node);
 
-  # Clean out crunch_tmp/work, crunch_tmp/opt, crunch_tmp/src*
+  if (!defined $no_clear_tmp) {
+    # Clean out crunch_tmp/work, crunch_tmp/opt, crunch_tmp/src*
 
-  my $cleanpid = fork();
-  if ($cleanpid == 0)
-  {
-    srun (["srun", "--nodelist=$nodelist", "-D", $ENV{'TMPDIR'}],
-         ['bash', '-c', 'if mount | grep -q $JOB_WORK/; then sudo /bin/umount $JOB_WORK/* 2>/dev/null; fi; sleep 1; rm -rf $JOB_WORK $CRUNCH_TMP/opt $CRUNCH_TMP/src*']);
-    exit (1);
-  }
-  while (1)
-  {
-    last if $cleanpid == waitpid (-1, WNOHANG);
-    freeze_if_want_freeze ($cleanpid);
-    select (undef, undef, undef, 0.1);
+    my $cleanpid = fork();
+    if ($cleanpid == 0)
+    {
+      srun (["srun", "--nodelist=$nodelist", "-D", $ENV{'TMPDIR'}],
+           ['bash', '-c', 'if mount | grep -q $JOB_WORK/; then sudo /bin/umount $JOB_WORK/* 2>/dev/null; fi; sleep 1; rm -rf $JOB_WORK $CRUNCH_TMP/opt $CRUNCH_TMP/src*']);
+      exit (1);
+    }
+    while (1)
+    {
+      last if $cleanpid == waitpid (-1, WNOHANG);
+      freeze_if_want_freeze ($cleanpid);
+      select (undef, undef, undef, 0.1);
+    }
+    Log (undef, "Clean-work-dir exited $?");
   }
-  Log (undef, "Clean-work-dir exited $?");
 
   # Install requested code version
 
@@ -463,6 +485,12 @@ else
   Log (undef, "Install exited $?");
 }
 
+if (!$have_slurm)
+{
+  # Grab our lock again (we might have deleted and re-created CRUNCH_TMP above)
+  must_lock_now("$ENV{CRUNCH_TMP}/.lock", "a job is already running here.");
+}
+
 
 
 foreach (qw (script script_version script_parameters runtime_constraints))
@@ -1346,6 +1374,15 @@ sub ban_node_by_slot {
   Log (undef, "backing off node " . $slot[$slotid]->{node}->{name} . " for 60 seconds");
 }
 
+sub must_lock_now
+{
+  my ($lockfile, $error_message) = @_;
+  open L, ">", $lockfile or croak("$lockfile: $!");
+  if (!flock L, LOCK_EX|LOCK_NB) {
+    croak("Can't lock $lockfile: $error_message\n");
+  }
+}
+
 __DATA__
 #!/usr/bin/perl
 
index c4a7bd6a7603af1f44dfeec78fd4c4e1b680fa37..b6e0f67796b8a9a2022643f2565ee621bd2b4c25 100755 (executable)
@@ -310,7 +310,7 @@ class Dispatcher
 
     jobrecord = Job.find_by_uuid(job_done.uuid)
     jobrecord.running = false
-    jobrecord.finished_at ||= Time.now,
+    jobrecord.finished_at ||= Time.now
     # Don't set 'jobrecord.success = false' because if the job failed to run due to an
     # issue with crunch-job or slurm, we want the job to stay in the queue.
     jobrecord.save!
index 88627f559de4d507b3fac87c53c4aab111a235ec..2167b0431882ef9eeef5acd733dc3b03312c6f34 100755 (executable)
@@ -21,10 +21,6 @@ module Rails
                 :config => File.expand_path("config.ru"),
                 :SSLEnable => true,
                 :SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE,
-                :SSLPrivateKey => OpenSSL::PKey::RSA.new(
-                       File.open("config/api.clinicalfuture.com.key.pem").read),
-                :SSLCertificate => OpenSSL::X509::Certificate.new(
-                       File.open("config/api.clinicalfuture.com.crt.pem").read),
                 :SSLCertName => [["CN", WEBrick::Utils::getservername]]
             })
         end
diff --git a/services/api/test/integration/permissions_test.rb b/services/api/test/integration/permissions_test.rb
new file mode 100644 (file)
index 0000000..c6597d5
--- /dev/null
@@ -0,0 +1,244 @@
+require 'test_helper'
+
+class PermissionsTest < ActionDispatch::IntegrationTest
+  fixtures :users, :groups, :api_client_authorizations, :collections
+
+  test "adding and removing direct can_read links" do
+    auth = {'HTTP_AUTHORIZATION' => "OAuth2 #{api_client_authorizations(:spectator).api_token}"}
+    admin_auth = {'HTTP_AUTHORIZATION' => "OAuth2 #{api_client_authorizations(:admin).api_token}"}
+
+    # try to read collection as spectator
+    get "/arvados/v1/collections/#{collections(:foo_file).uuid}", {:format => :json}, auth
+    assert_response 404
+
+    # try to add permission as spectator
+    post "/arvados/v1/links", {
+      :format => :json,
+      :link => {
+        tail_kind: 'arvados#user',
+        tail_uuid: users(:spectator).uuid,
+        link_class: 'permission',
+        name: 'can_read',
+        head_kind: 'arvados#collection',
+        head_uuid: collections(:foo_file).uuid,
+        properties: {}
+      }
+    }, auth
+    assert_response 422
+
+    # add permission as admin
+    post "/arvados/v1/links", {
+      :format => :json,
+      :link => {
+        tail_kind: 'arvados#user',
+        tail_uuid: users(:spectator).uuid,
+        link_class: 'permission',
+        name: 'can_read',
+        head_kind: 'arvados#collection',
+        head_uuid: collections(:foo_file).uuid,
+        properties: {}
+      }
+    }, admin_auth
+    u = jresponse['uuid']
+    assert_response :success
+
+    # read collection as spectator
+    get "/arvados/v1/collections/#{collections(:foo_file).uuid}", {:format => :json}, auth
+    assert_response :success
+
+    # try to delete permission as spectator
+    delete "/arvados/v1/links/#{u}", {:format => :json}, auth
+    assert_response 403
+
+    # delete permission as admin
+    delete "/arvados/v1/links/#{u}", {:format => :json}, admin_auth
+    assert_response :success
+
+    # try to read collection as spectator
+    get "/arvados/v1/collections/#{collections(:foo_file).uuid}", {:format => :json}, auth
+    assert_response 404
+  end
+
+
+  test "adding can_read links from user to group, group to collection" do
+    auth = {'HTTP_AUTHORIZATION' => "OAuth2 #{api_client_authorizations(:spectator).api_token}"}
+    admin_auth = {'HTTP_AUTHORIZATION' => "OAuth2 #{api_client_authorizations(:admin).api_token}"}
+    
+    # try to read collection as spectator
+    get "/arvados/v1/collections/#{collections(:foo_file).uuid}", {:format => :json}, auth
+    assert_response 404
+
+    # add permission for spectator to read group
+    post "/arvados/v1/links", {
+      :format => :json,
+      :link => {
+        tail_kind: 'arvados#user',
+        tail_uuid: users(:spectator).uuid,
+        link_class: 'permission',
+        name: 'can_read',
+        head_kind: 'arvados#group',
+        head_uuid: groups(:private).uuid,
+        properties: {}
+      }
+    }, admin_auth
+    assert_response :success
+
+    # try to read collection as spectator
+    get "/arvados/v1/collections/#{collections(:foo_file).uuid}", {:format => :json}, auth
+    assert_response 404
+
+    # add permission for group to read collection
+    post "/arvados/v1/links", {
+      :format => :json,
+      :link => {
+        tail_kind: 'arvados#group',
+        tail_uuid: groups(:private).uuid,
+        link_class: 'permission',
+        name: 'can_read',
+        head_kind: 'arvados#collection',
+        head_uuid: collections(:foo_file).uuid,
+        properties: {}
+      }
+    }, admin_auth
+    u = jresponse['uuid']
+    assert_response :success
+
+    # try to read collection as spectator
+    get "/arvados/v1/collections/#{collections(:foo_file).uuid}", {:format => :json}, auth
+    assert_response :success
+
+    # delete permission for group to read collection
+    delete "/arvados/v1/links/#{u}", {:format => :json}, admin_auth
+    assert_response :success
+
+    # try to read collection as spectator
+    get "/arvados/v1/collections/#{collections(:foo_file).uuid}", {:format => :json}, auth
+    assert_response 404
+    
+  end
+
+
+  test "adding can_read links from group to collection, user to group" do
+    auth = {'HTTP_AUTHORIZATION' => "OAuth2 #{api_client_authorizations(:spectator).api_token}"}
+    admin_auth = {'HTTP_AUTHORIZATION' => "OAuth2 #{api_client_authorizations(:admin).api_token}"}
+    
+    # try to read collection as spectator
+    get "/arvados/v1/collections/#{collections(:foo_file).uuid}", {:format => :json}, auth
+    assert_response 404
+
+    # add permission for group to read collection
+    post "/arvados/v1/links", {
+      :format => :json,
+      :link => {
+        tail_kind: 'arvados#group',
+        tail_uuid: groups(:private).uuid,
+        link_class: 'permission',
+        name: 'can_read',
+        head_kind: 'arvados#collection',
+        head_uuid: collections(:foo_file).uuid,
+        properties: {}
+      }
+    }, admin_auth
+    assert_response :success
+
+    # try to read collection as spectator
+    get "/arvados/v1/collections/#{collections(:foo_file).uuid}", {:format => :json}, auth
+    assert_response 404
+
+    # add permission for spectator to read group
+    post "/arvados/v1/links", {
+      :format => :json,
+      :link => {
+        tail_kind: 'arvados#user',
+        tail_uuid: users(:spectator).uuid,
+        link_class: 'permission',
+        name: 'can_read',
+        head_kind: 'arvados#group',
+        head_uuid: groups(:private).uuid,
+        properties: {}
+      }
+    }, admin_auth
+    u = jresponse['uuid']
+    assert_response :success
+
+    # try to read collection as spectator
+    get "/arvados/v1/collections/#{collections(:foo_file).uuid}", {:format => :json}, auth
+    assert_response :success
+
+    # delete permission for spectator to read group
+    delete "/arvados/v1/links/#{u}", {:format => :json}, admin_auth
+    assert_response :success
+
+    # try to read collection as spectator
+    get "/arvados/v1/collections/#{collections(:foo_file).uuid}", {:format => :json}, auth
+    assert_response 404
+    
+  end
+
+  test "adding can_read links from user to group, group to group, group to collection" do
+    auth = {'HTTP_AUTHORIZATION' => "OAuth2 #{api_client_authorizations(:spectator).api_token}"}
+    admin_auth = {'HTTP_AUTHORIZATION' => "OAuth2 #{api_client_authorizations(:admin).api_token}"}
+    
+    # try to read collection as spectator
+    get "/arvados/v1/collections/#{collections(:foo_file).uuid}", {:format => :json}, auth
+    assert_response 404
+
+    # add permission for user to read group
+    post "/arvados/v1/links", {
+      :format => :json,
+      :link => {
+        tail_kind: 'arvados#user',
+        tail_uuid: users(:spectator).uuid,
+        link_class: 'permission',
+        name: 'can_read',
+        head_kind: 'arvados#group',
+        head_uuid: groups(:private).uuid,
+        properties: {}
+      }
+    }, admin_auth
+    assert_response :success
+
+    # add permission for group to read group
+    post "/arvados/v1/links", {
+      :format => :json,
+      :link => {
+        tail_kind: 'arvados#group',
+        tail_uuid: groups(:private).uuid,
+        link_class: 'permission',
+        name: 'can_read',
+        head_kind: 'arvados#group',
+        head_uuid: groups(:empty_lonely_group).uuid,
+        properties: {}
+      }
+    }, admin_auth
+    assert_response :success
+
+    # add permission for group to read collection
+    post "/arvados/v1/links", {
+      :format => :json,
+      :link => {
+        tail_kind: 'arvados#group',
+        tail_uuid: groups(:empty_lonely_group).uuid,
+        link_class: 'permission',
+        name: 'can_read',
+        head_kind: 'arvados#collection',
+        head_uuid: collections(:foo_file).uuid,
+        properties: {}
+      }
+    }, admin_auth
+    u = jresponse['uuid']
+    assert_response :success
+
+    # try to read collection as spectator
+    get "/arvados/v1/collections/#{collections(:foo_file).uuid}", {:format => :json}, auth
+    assert_response :success
+
+    # delete permission for group to read collection
+    delete "/arvados/v1/links/#{u}", {:format => :json}, admin_auth
+    assert_response :success
+
+    # try to read collection as spectator
+    get "/arvados/v1/collections/#{collections(:foo_file).uuid}", {:format => :json}, auth
+    assert_response 404
+  end
+end