Merge branch '11095-cwl-control-reuse' closes #11095
authorPeter Amstutz <peter.amstutz@curoverse.com>
Wed, 28 Jun 2017 13:23:13 +0000 (09:23 -0400)
committerPeter Amstutz <peter.amstutz@curoverse.com>
Wed, 28 Jun 2017 13:23:13 +0000 (09:23 -0400)
20 files changed:
apps/workbench/app/controllers/collections_controller.rb
apps/workbench/app/models/collection.rb
apps/workbench/app/views/collections/_show_tag_rows.html.erb
apps/workbench/test/controllers/collections_controller_test.rb
apps/workbench/test/controllers/trash_items_controller_test.rb [new file with mode: 0644]
apps/workbench/test/integration/collections_test.rb
doc/user/tutorials/tutorial-keep-collection-lifecycle.html.textile.liquid
sdk/go/arvadostest/fixtures.go
services/api/app/controllers/arvados/v1/collections_controller.rb
services/api/app/controllers/arvados/v1/schema_controller.rb
services/api/test/fixtures/collections.yml
services/api/test/functional/arvados/v1/collections_controller_test.rb
services/ws/config.go
services/ws/event.go
services/ws/event_source.go
services/ws/event_source_test.go
services/ws/router.go
services/ws/server_test.go
services/ws/session_v0.go
services/ws/session_v0_test.go

index 99399bc9c2dc745c7d04e9842039346b7c082bab..515cdf1867fba103fce298daf7cf2ecd755d1f8d 100644 (file)
@@ -360,10 +360,7 @@ class CollectionsController < ApplicationController
     end
 
     if tags
-      props = @object.properties
-      props[:tags] = tags
-
-      if @object.update_attributes properties: props
+      if @object.update_attributes properties: tags
         @saved_tags = true
         render
       else
index 305ea015306fe898c97bd16b443a13d9ff230781..025c136d41bed69883a80d0cc12cbc3bfdf43ec9 100644 (file)
@@ -99,6 +99,6 @@ class Collection < ArvadosBase
   end
 
   def untrash
-    arvados_api_client.api(self.class, "/#{self.uuid}/untrash", {})
+    arvados_api_client.api(self.class, "/#{self.uuid}/untrash", {"ensure_unique_name" => true})
   end
 end
index eceec1057beb05d55c8088fab6b694b4170feea0..293f02bfb01451c4be219f9d3f6134380b2325e0 100644 (file)
@@ -1,5 +1,5 @@
 <%
-  tags = object.properties[:tags]
+  tags = object.properties
 %>
       <% if tags.andand.is_a?(Hash) %>
         <% tags.each do |k, v| %>
index 63e1ae3972755b6a9f9df3d476e0dd7037417b58..ce6adbcf32d552e98efd0c340dff850cd8436f1e 100644 (file)
@@ -828,15 +828,12 @@ class CollectionsControllerTest < ActionController::TestCase
     assert_equal false, response.body.include?("existing tag 1")
     assert_equal false, response.body.include?("value for existing tag 1")
 
-    updated_properties = Collection.find(collection['uuid']).properties
-    updated_tags = updated_properties[:tags]
+    updated_tags = Collection.find(collection['uuid']).properties
     assert_equal true, updated_tags.keys.include?(:'new_tag1')
     assert_equal new_tags['new_tag1'], updated_tags[:'new_tag1']
     assert_equal true, updated_tags.keys.include?(:'new_tag2')
     assert_equal new_tags['new_tag2'], updated_tags[:'new_tag2']
     assert_equal false, updated_tags.keys.include?(:'existing tag 1')
     assert_equal false, updated_tags.keys.include?(:'existing tag 2')
-    assert_equal true, updated_properties.keys.include?(:'some other property')
-    assert_equal 'value for the other property', updated_properties[:'some other property']
   end
 end
diff --git a/apps/workbench/test/controllers/trash_items_controller_test.rb b/apps/workbench/test/controllers/trash_items_controller_test.rb
new file mode 100644 (file)
index 0000000..c530ba9
--- /dev/null
@@ -0,0 +1,14 @@
+require 'test_helper'
+
+class TrashItemsControllerTest < ActionController::TestCase
+  test "untrash collection with same name as another collection" do
+    collection = api_fixture('collections')['trashed_collection_to_test_name_conflict_on_untrash']
+    items = [collection['uuid']]
+    post :untrash_items, {
+      selection: items,
+      format: :js
+    }, session_for(:active)
+
+    assert_response :success
+  end
+end
index dbdcef8fbee5461086d4a583a8246748483be98f..fcf47ad02340df25b05d900d79f42c67255b8ccc 100644 (file)
@@ -89,11 +89,11 @@ class CollectionsTest < ActionDispatch::IntegrationTest
     assert(page.has_text?(foo_collection['uuid']), "Collection page did not include foo file")
     assert(page.has_text?(bar_collection['uuid']), "Collection page did not include bar file")
 
-    within('tr', text: foo_collection['uuid']) do
+    within "tr[data-object-uuid=\"#{foo_collection['uuid']}\"]" do
       find('input[type=checkbox]').click
     end
 
-    within('tr', text: bar_collection['uuid']) do
+    within "tr[data-object-uuid=\"#{bar_collection['uuid']}\"]" do
       find('input[type=checkbox]').click
     end
 
index 1bc775a701e46f54992859cda8b1039b23fa7a4f..c32c0579742fa7ad14ecabe86795f3c72c428fbf 100644 (file)
@@ -21,7 +21,7 @@ h2(#collection_attributes). Collection lifecycle attributes
 As listed above the attributes that are used to manage a collection lifecycle are it's *is_trashed*, *trash_at*, and *delete_at*. The table below lists the values of these attributes and how they influence the state of a collection and it's accessibility.
 
 table(table table-bordered table-condensed).
-|_. collection state|_. is_trashed|_. trash_at|_. delete_at|_. get|_. index|_. index?include_trash=true|_. can be modified|
+|_. collection state|_. is_trashed|_. trash_at|_. delete_at|_. get|_. list|_. list?include_trash=true|_. can be modified|
 |persisted collection|false |null |null |yes |yes |yes |yes |
 |expiring collection|false |future |future |yes  |yes |yes |yes |
 |trashed collection|true |past |future |no |no |yes |only is_trashed, trash_at and delete_at attribtues|
@@ -47,10 +47,10 @@ A collection can be un-trashed / recovered using either the arv command line too
 
 h3. Un-trashing a collection using arv command line tool
 
-You can list the trashed collections using the index command.
+You can list the trashed collections using the list command.
 
 <pre>
-arv collection index --include-trash=true --filters '[["is_trashed", "=", "true"]]'
+arv collection list --include-trash=true --filters '[["is_trashed", "=", "true"]]'
 </pre>
 
 You can then untrash a particular collection using arv using it's uuid.
index 7e21da4982b3ecb2325f3117008e15a7a8513a7d..cdab4633b45b8cba0922f0a185aab1948c746342 100644 (file)
@@ -7,6 +7,7 @@ const (
        AdminToken              = "4axaw8zxe0qm22wa6urpp5nskcne8z88cvbupv653y1njyi05h"
        AnonymousToken          = "4kg6k6lzmp9kj4cpkcoxie964cmvjahbt4fod9zru44k4jqdmi"
        DataManagerToken        = "320mkve8qkswstz7ff61glpk3mhgghmg67wmic7elw4z41pke1"
+       ManagementToken         = "jg3ajndnq63sywcd50gbs5dskdc9ckkysb0nsqmfz08nwf17nl"
        ActiveUserUUID          = "zzzzz-tpzed-xurymjxw79nv3jz"
        SpectatorUserUUID       = "zzzzz-tpzed-l1s2piq4t4mps8r"
        UserAgreementCollection = "zzzzz-4zz18-uukreo9rbgwsujr" // user_agreement_in_anonymously_accessible_project
index 5c09b1fccdf09f0af508490ac34803efd5b61237..b5dd07e064a71e003b66e81030134dce460e7d4f 100644 (file)
@@ -75,7 +75,13 @@ class Arvados::V1::CollectionsController < ApplicationController
 
   def untrash
     if @object.is_trashed
-      @object.update_attributes!(trash_at: nil)
+      @object.trash_at = nil
+
+      if params[:ensure_unique_name]
+        @object.save_with_unique_name!
+      else
+        @object.save!
+      end
     else
       raise InvalidStateTransitionError
     end
index e1f4ca5770403e1eb9164ed41d59e807ed1cc551..61ad02bf4ad0e478d12b398cb506ab03ca1ceccf 100644 (file)
@@ -189,14 +189,14 @@ class Arvados::V1::SchemaController < ApplicationController
                        "https://api.curoverse.com/auth/arvados.readonly"
                       ]
             },
-            list: {
-              id: "arvados.#{k.to_s.underscore.pluralize}.list",
+            index: {
+              id: "arvados.#{k.to_s.underscore.pluralize}.index",
               path: k.to_s.underscore.pluralize,
               httpMethod: "GET",
               description:
-                 %|List #{k.to_s.pluralize}.
+                 %|Index #{k.to_s.pluralize}.
 
-                   The <code>list</code> method returns a
+                   The <code>index</code> method returns a
                    <a href="/api/resources.html">resource list</a> of
                    matching #{k.to_s.pluralize}. For example:
 
@@ -216,53 +216,6 @@ class Arvados::V1::SchemaController < ApplicationController
                     }
                     </pre>|,
               parameters: {
-                limit: {
-                  type: "integer",
-                  description: "Maximum number of #{k.to_s.underscore.pluralize} to return.",
-                  default: "100",
-                  format: "int32",
-                  minimum: "0",
-                  location: "query",
-                },
-                offset: {
-                  type: "integer",
-                  description: "Number of #{k.to_s.underscore.pluralize} to skip before first returned record.",
-                  default: "0",
-                  format: "int32",
-                  minimum: "0",
-                  location: "query",
-                  },
-                filters: {
-                  type: "array",
-                  description: "Conditions for filtering #{k.to_s.underscore.pluralize}.",
-                  location: "query"
-                },
-                where: {
-                  type: "object",
-                  description: "Conditions for filtering #{k.to_s.underscore.pluralize}. (Deprecated. Use filters instead.)",
-                  location: "query"
-                },
-                order: {
-                  type: "string",
-                  description: "Order in which to return matching #{k.to_s.underscore.pluralize}.",
-                  location: "query"
-                },
-                select: {
-                  type: "array",
-                  description: "Select which fields to return.",
-                  location: "query"
-                },
-                distinct: {
-                  type: "boolean",
-                  description: "Return each distinct object.",
-                  location: "query"
-                },
-                count: {
-                  type: "string",
-                  description: "Type of count to return in items_available ('none' or 'exact').",
-                  default: "exact",
-                  location: "query"
-                }
               },
               response: {
                 "$ref" => "#{k.to_s}List"
@@ -405,6 +358,14 @@ class Arvados::V1::SchemaController < ApplicationController
               end
             end
             d_methods[action.to_sym] = method
+
+            if action == 'index'
+              list_method = method.dup
+              list_method[:id].sub!('index', 'list')
+              list_method[:description].sub!('Index', 'List')
+              list_method[:description].sub!('index', 'list')
+              d_methods[:list] = list_method
+            end
           end
         end
       end
index 6543f5422a861b3430c285402a5ae1d8ac1d3dd4..f8d79bd79e317152771ac84e575a106ab46dfcdb 100644 (file)
@@ -26,11 +26,11 @@ foo_file:
   uuid: zzzzz-4zz18-znfnqtbbv4spc3w
   portable_data_hash: 1f4b0bc7583c2a7f9102c395f4ffc5e3+45
   owner_uuid: zzzzz-tpzed-000000000000000
-  created_at: 2014-02-03T17:22:54Z
+  created_at: 2015-02-03T17:22:54Z
   modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
   modified_by_user_uuid: zzzzz-tpzed-d9tiejq69daie8f
-  modified_at: 2014-02-03T17:22:54Z
-  updated_at: 2014-02-03T17:22:54Z
+  modified_at: 2015-02-03T17:22:54Z
+  updated_at: 2015-02-03T17:22:54Z
   manifest_text: ". acbd18db4cc2f85cedef654fccc4a4d8+3 0:3:foo\n"
   name: foo_file
 
@@ -38,11 +38,11 @@ bar_file:
   uuid: zzzzz-4zz18-ehbhgtheo8909or
   portable_data_hash: fa7aeb5140e2848d39b416daeef4ffc5+45
   owner_uuid: zzzzz-tpzed-000000000000000
-  created_at: 2014-02-03T17:22:54Z
+  created_at: 2015-02-03T17:22:54Z
   modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
   modified_by_user_uuid: zzzzz-tpzed-d9tiejq69daie8f
-  modified_at: 2014-02-03T17:22:54Z
-  updated_at: 2014-02-03T17:22:54Z
+  modified_at: 2015-02-03T17:22:54Z
+  updated_at: 2015-02-03T17:22:54Z
   manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
   name: bar_file
 
@@ -669,10 +669,35 @@ collection_with_tags_owned_by_active:
   manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
   name: collection with tags
   properties:
-    tags:
-      existing tag 1: value for existing tag 1
-      existing tag 2: value for existing tag 2
-    some other property: value for the other property
+    existing tag 1: value for existing tag 1
+    existing tag 2: value for existing tag 2
+
+trashed_collection_to_test_name_conflict_on_untrash:
+  uuid: zzzzz-4zz18-trashedcolnamec
+  portable_data_hash: 80cf6dd2cf079dd13f272ec4245cb4a8+48
+  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
+  created_at: 2014-02-03T17:22:54Z
+  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
+  modified_by_user_uuid: zzzzz-tpzed-d9tiejq69daie8f
+  modified_at: 2014-02-03T17:22:54Z
+  updated_at: 2014-02-03T17:22:54Z
+  manifest_text: ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:file1 0:0:file2\n"
+  name: same name for trashed and persisted collections
+  is_trashed: true
+  trash_at: 2001-01-01T00:00:00Z
+  delete_at: 2038-01-01T00:00:00Z
+
+same_name_as_trashed_coll_to_test_name_conflict_on_untrash:
+  uuid: zzzzz-4zz18-namesameastrash
+  portable_data_hash: 80cf6dd2cf079dd13f272ec4245cb4a8+48
+  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
+  created_at: 2014-02-03T17:22:54Z
+  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
+  modified_by_user_uuid: zzzzz-tpzed-d9tiejq69daie8f
+  modified_at: 2014-02-03T17:22:54Z
+  updated_at: 2014-02-03T17:22:54Z
+  manifest_text: ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:file1 0:0:file2\n"
+  name: same name for trashed and persisted collections
 
 
 # Test Helper trims the rest of the file
index 17af916b3df244821bc596bbbc58fad799d8d380..70f35f3dd2b54f51f00edee2deed4aa8dd265e3b 100644 (file)
@@ -1065,4 +1065,25 @@ EOS
       end
     end
   end
+
+  test 'untrash collection with same name as another with no ensure unique name' do
+    authorize_with :active
+    post :untrash, {
+      id: collections(:trashed_collection_to_test_name_conflict_on_untrash).uuid,
+    }
+    assert_response 422
+  end
+
+  test 'untrash collection with same name as another with ensure unique name' do
+    authorize_with :active
+    post :untrash, {
+      id: collections(:trashed_collection_to_test_name_conflict_on_untrash).uuid,
+      ensure_unique_name: true
+    }
+    assert_response 200
+    assert_equal false, json_response['is_trashed']
+    assert_nil json_response['trash_at']
+    assert_nil json_response['delete_at']
+    assert_match /^same name for trashed and persisted collections \(\d{4}-\d\d-\d\d.*?Z\)$/, json_response['name']
+  end
 end
index 79c2f232daf5059c4b51f7d1bb2fa27a592c5f64..cf82cf8e1064cae202132f8ee97056b72fef2e41 100644 (file)
@@ -17,6 +17,8 @@ type wsConfig struct {
        PingTimeout      arvados.Duration
        ClientEventQueue int
        ServerEventQueue int
+
+       ManagementToken string
 }
 
 func defaultConfig() wsConfig {
index 304f86bbd0583146c4f88a4499d98d872bdab8ea..fd280aebb91aab14700d1ef93395e9ca31becfbd 100644 (file)
@@ -17,6 +17,7 @@ type eventSink interface {
 type eventSource interface {
        NewSink() eventSink
        DB() *sql.DB
+       DBHealth() error
 }
 
 type event struct {
index 7c1b58492dd030ef6b579abe8b699d787a758cc2..daf9a94cc180d8503bca99bedd8a3c6b946c09e6 100644 (file)
@@ -242,6 +242,12 @@ func (ps *pgEventSource) DB() *sql.DB {
        return ps.db
 }
 
+func (ps *pgEventSource) DBHealth() error {
+       ctx, _ := context.WithDeadline(context.Background(), time.Now().Add(time.Second))
+       var i int
+       return ps.db.QueryRowContext(ctx, "SELECT 1").Scan(&i)
+}
+
 func (ps *pgEventSource) DebugStatus() interface{} {
        ps.mtx.Lock()
        defer ps.mtx.Unlock()
@@ -257,6 +263,7 @@ func (ps *pgEventSource) DebugStatus() interface{} {
                "QueueDelay":   stats.Duration(ps.lastQDelay),
                "Sinks":        len(ps.sinks),
                "SinksBlocked": blocked,
+               "DBStats":      ps.db.Stats(),
        }
 }
 
index b157cfa0eb9cf64cb1d7fc5566ea5649246e8a29..94e3ba3ea0e9ac61e376a4fad212182f5de15a0e 100644 (file)
@@ -105,4 +105,6 @@ func (*eventSourceSuite) TestEventSource(c *check.C) {
        case <-time.After(10 * time.Second):
                c.Fatal("timed out")
        }
+
+       c.Check(pges.DBHealth(), check.IsNil)
 }
index 15b825f2abfa293f8ba2734f57508ba9306a03af..77744974d32b7bb45905268461280e8444ca91e6 100644 (file)
@@ -53,8 +53,13 @@ func (rtr *router) setup() {
        rtr.mux = http.NewServeMux()
        rtr.mux.Handle("/websocket", rtr.makeServer(newSessionV0))
        rtr.mux.Handle("/arvados/v1/events.ws", rtr.makeServer(newSessionV1))
-       rtr.mux.HandleFunc("/debug.json", jsonHandler(rtr.DebugStatus))
-       rtr.mux.HandleFunc("/status.json", jsonHandler(rtr.Status))
+       rtr.mux.Handle("/debug.json", rtr.jsonHandler(rtr.DebugStatus))
+       rtr.mux.Handle("/status.json", rtr.jsonHandler(rtr.Status))
+
+       health := http.NewServeMux()
+       rtr.mux.Handle("/_health/", rtr.mgmtAuth(health))
+       health.Handle("/_health/ping", rtr.jsonHandler(rtr.HealthFunc(func() error { return nil })))
+       health.Handle("/_health/db", rtr.jsonHandler(rtr.HealthFunc(rtr.eventSource.DBHealth)))
 }
 
 func (rtr *router) makeServer(newSession sessionFactory) *websocket.Server {
@@ -102,6 +107,21 @@ func (rtr *router) DebugStatus() interface{} {
        return s
 }
 
+var pingResponseOK = map[string]string{"health": "OK"}
+
+func (rtr *router) HealthFunc(f func() error) func() interface{} {
+       return func() interface{} {
+               err := f()
+               if err == nil {
+                       return pingResponseOK
+               }
+               return map[string]string{
+                       "health": "ERROR",
+                       "error":  err.Error(),
+               }
+       }
+}
+
 func (rtr *router) Status() interface{} {
        return map[string]interface{}{
                "Clients": atomic.LoadInt64(&rtr.status.ReqsActive),
@@ -125,16 +145,30 @@ func (rtr *router) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
        rtr.mux.ServeHTTP(resp, req)
 }
 
-func jsonHandler(fn func() interface{}) http.HandlerFunc {
-       return func(resp http.ResponseWriter, req *http.Request) {
-               logger := logger(req.Context())
-               resp.Header().Set("Content-Type", "application/json")
-               enc := json.NewEncoder(resp)
+func (rtr *router) mgmtAuth(h http.Handler) http.Handler {
+       return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+               if rtr.Config.ManagementToken == "" {
+                       http.Error(w, "disabled", http.StatusNotFound)
+               } else if ah := r.Header.Get("Authorization"); ah == "" {
+                       http.Error(w, "authorization required", http.StatusUnauthorized)
+               } else if ah != "Bearer "+rtr.Config.ManagementToken {
+                       http.Error(w, "authorization error", http.StatusForbidden)
+               } else {
+                       h.ServeHTTP(w, r)
+               }
+       })
+}
+
+func (rtr *router) jsonHandler(fn func() interface{}) http.Handler {
+       return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+               logger := logger(r.Context())
+               w.Header().Set("Content-Type", "application/json")
+               enc := json.NewEncoder(w)
                err := enc.Encode(fn())
                if err != nil {
                        msg := "encode failed"
                        logger.WithError(err).Error(msg)
-                       http.Error(resp, msg, http.StatusInternalServerError)
+                       http.Error(w, msg, http.StatusInternalServerError)
                }
-       }
+       })
 }
index d74f7dff4283ef071fb4e1ae6cb4d8e5db1c9f07..3e19b690b55234861a3464b73a2c736a784219e5 100644 (file)
@@ -1,43 +1,53 @@
 package main
 
 import (
+       "io/ioutil"
+       "net/http"
        "sync"
        "time"
 
        "git.curoverse.com/arvados.git/sdk/go/arvados"
+       "git.curoverse.com/arvados.git/sdk/go/arvadostest"
        check "gopkg.in/check.v1"
 )
 
 var _ = check.Suite(&serverSuite{})
 
 type serverSuite struct {
+       cfg *wsConfig
+       srv *server
+       wg  sync.WaitGroup
 }
 
-func testConfig() *wsConfig {
+func (s *serverSuite) SetUpTest(c *check.C) {
+       s.cfg = s.testConfig()
+       s.srv = &server{wsConfig: s.cfg}
+}
+
+func (*serverSuite) testConfig() *wsConfig {
        cfg := defaultConfig()
        cfg.Client = *(arvados.NewClientFromEnv())
        cfg.Postgres = testDBConfig()
        cfg.Listen = ":"
+       cfg.ManagementToken = arvadostest.ManagementToken
        return &cfg
 }
 
 // TestBadDB ensures Run() returns an error (instead of panicking or
 // deadlocking) if it can't connect to the database server at startup.
 func (s *serverSuite) TestBadDB(c *check.C) {
-       cfg := testConfig()
-       cfg.Postgres["password"] = "1234"
-       srv := &server{wsConfig: cfg}
+       s.cfg.Postgres["password"] = "1234"
 
        var wg sync.WaitGroup
        wg.Add(1)
        go func() {
-               err := srv.Run()
+               err := s.srv.Run()
                c.Check(err, check.NotNil)
                wg.Done()
        }()
        wg.Add(1)
        go func() {
-               srv.WaitReady()
+               s.srv.WaitReady()
                wg.Done()
        }()
 
@@ -53,9 +63,42 @@ func (s *serverSuite) TestBadDB(c *check.C) {
        }
 }
 
-func newTestServer() *server {
-       srv := &server{wsConfig: testConfig()}
-       go srv.Run()
-       srv.WaitReady()
-       return srv
+func (s *serverSuite) TestHealth(c *check.C) {
+       go s.srv.Run()
+       defer s.srv.Close()
+       s.srv.WaitReady()
+       for _, token := range []string{"", "foo", s.cfg.ManagementToken} {
+               req, err := http.NewRequest("GET", "http://"+s.srv.listener.Addr().String()+"/_health/ping", nil)
+               c.Assert(err, check.IsNil)
+               if token != "" {
+                       req.Header.Add("Authorization", "Bearer "+token)
+               }
+               resp, err := http.DefaultClient.Do(req)
+               c.Check(err, check.IsNil)
+               if token == s.cfg.ManagementToken {
+                       c.Check(resp.StatusCode, check.Equals, http.StatusOK)
+                       buf, err := ioutil.ReadAll(resp.Body)
+                       c.Check(err, check.IsNil)
+                       c.Check(string(buf), check.Equals, `{"health":"OK"}`+"\n")
+               } else {
+                       c.Check(resp.StatusCode, check.Not(check.Equals), http.StatusOK)
+               }
+       }
+}
+
+func (s *serverSuite) TestHealthDisabled(c *check.C) {
+       s.cfg.ManagementToken = ""
+
+       go s.srv.Run()
+       defer s.srv.Close()
+       s.srv.WaitReady()
+
+       for _, token := range []string{"", "foo", arvadostest.ManagementToken} {
+               req, err := http.NewRequest("GET", "http://"+s.srv.listener.Addr().String()+"/_health/ping", nil)
+               c.Assert(err, check.IsNil)
+               req.Header.Add("Authorization", "Bearer "+token)
+               resp, err := http.DefaultClient.Do(req)
+               c.Check(err, check.IsNil)
+               c.Check(resp.StatusCode, check.Equals, http.StatusNotFound)
+       }
 }
index 44e2a1deb5843f5909fc6f9157bbde1ca045e424..f8645eb887ed735fea07845f2b4ea0c1a5ae4da7 100644 (file)
@@ -157,6 +157,7 @@ func (sub *v0subscribe) sendOldEvents(sess *v0session) {
                sess.log.WithError(err).Error("db.Query failed")
                return
        }
+       defer rows.Close()
        for rows.Next() {
                var id uint64
                err := rows.Scan(&id)
index 85e36560e8d19d8364a8b531b89b89a356948bd0..f6fe3f60e6bcd929ac917bc469d3b86cf2a542c7 100644 (file)
@@ -25,11 +25,13 @@ func init() {
 var _ = check.Suite(&v0Suite{})
 
 type v0Suite struct {
-       token    string
-       toDelete []string
+       serverSuite serverSuite
+       token       string
+       toDelete    []string
 }
 
 func (s *v0Suite) SetUpTest(c *check.C) {
+       s.serverSuite.SetUpTest(c)
        s.token = arvadostest.ActiveToken
 }
 
@@ -227,7 +229,9 @@ func (s *v0Suite) expectLog(c *check.C, r *json.Decoder) *arvados.Log {
 }
 
 func (s *v0Suite) testClient() (*server, *websocket.Conn, *json.Decoder, *json.Encoder) {
-       srv := newTestServer()
+       go s.serverSuite.srv.Run()
+       s.serverSuite.srv.WaitReady()
+       srv := s.serverSuite.srv
        conn, err := websocket.Dial("ws://"+srv.listener.Addr().String()+"/websocket?api_token="+s.token, "", "http://"+srv.listener.Addr().String())
        if err != nil {
                panic(err)