From: Peter Amstutz Date: Wed, 28 Jun 2017 13:23:13 +0000 (-0400) Subject: Merge branch '11095-cwl-control-reuse' closes #11095 X-Git-Tag: 1.1.0~169 X-Git-Url: https://git.arvados.org/arvados.git/commitdiff_plain/a27e0874320e408124fee837b355995350bbc7ee?hp=80459d52161120ae8e33da140984d596271d5195 Merge branch '11095-cwl-control-reuse' closes #11095 --- diff --git a/apps/workbench/app/controllers/collections_controller.rb b/apps/workbench/app/controllers/collections_controller.rb index 99399bc9c2..515cdf1867 100644 --- a/apps/workbench/app/controllers/collections_controller.rb +++ b/apps/workbench/app/controllers/collections_controller.rb @@ -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 diff --git a/apps/workbench/app/models/collection.rb b/apps/workbench/app/models/collection.rb index 305ea01530..025c136d41 100644 --- a/apps/workbench/app/models/collection.rb +++ b/apps/workbench/app/models/collection.rb @@ -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 diff --git a/apps/workbench/app/views/collections/_show_tag_rows.html.erb b/apps/workbench/app/views/collections/_show_tag_rows.html.erb index eceec1057b..293f02bfb0 100644 --- a/apps/workbench/app/views/collections/_show_tag_rows.html.erb +++ b/apps/workbench/app/views/collections/_show_tag_rows.html.erb @@ -1,5 +1,5 @@ <% - tags = object.properties[:tags] + tags = object.properties %> <% if tags.andand.is_a?(Hash) %> <% tags.each do |k, v| %> diff --git a/apps/workbench/test/controllers/collections_controller_test.rb b/apps/workbench/test/controllers/collections_controller_test.rb index 63e1ae3972..ce6adbcf32 100644 --- a/apps/workbench/test/controllers/collections_controller_test.rb +++ b/apps/workbench/test/controllers/collections_controller_test.rb @@ -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 index 0000000000..c530ba9f6d --- /dev/null +++ b/apps/workbench/test/controllers/trash_items_controller_test.rb @@ -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 diff --git a/apps/workbench/test/integration/collections_test.rb b/apps/workbench/test/integration/collections_test.rb index dbdcef8fbe..fcf47ad023 100644 --- a/apps/workbench/test/integration/collections_test.rb +++ b/apps/workbench/test/integration/collections_test.rb @@ -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 diff --git a/doc/user/tutorials/tutorial-keep-collection-lifecycle.html.textile.liquid b/doc/user/tutorials/tutorial-keep-collection-lifecycle.html.textile.liquid index 1bc775a701..c32c057974 100644 --- a/doc/user/tutorials/tutorial-keep-collection-lifecycle.html.textile.liquid +++ b/doc/user/tutorials/tutorial-keep-collection-lifecycle.html.textile.liquid @@ -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.
-arv collection index --include-trash=true --filters '[["is_trashed", "=", "true"]]'
+arv collection list --include-trash=true --filters '[["is_trashed", "=", "true"]]'
 
You can then untrash a particular collection using arv using it's uuid. diff --git a/sdk/go/arvadostest/fixtures.go b/sdk/go/arvadostest/fixtures.go index 7e21da4982..cdab4633b4 100644 --- a/sdk/go/arvadostest/fixtures.go +++ b/sdk/go/arvadostest/fixtures.go @@ -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 diff --git a/services/api/app/controllers/arvados/v1/collections_controller.rb b/services/api/app/controllers/arvados/v1/collections_controller.rb index 5c09b1fccd..b5dd07e064 100644 --- a/services/api/app/controllers/arvados/v1/collections_controller.rb +++ b/services/api/app/controllers/arvados/v1/collections_controller.rb @@ -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 diff --git a/services/api/app/controllers/arvados/v1/schema_controller.rb b/services/api/app/controllers/arvados/v1/schema_controller.rb index e1f4ca5770..61ad02bf4a 100644 --- a/services/api/app/controllers/arvados/v1/schema_controller.rb +++ b/services/api/app/controllers/arvados/v1/schema_controller.rb @@ -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 list method returns a + The index method returns a resource list of matching #{k.to_s.pluralize}. For example: @@ -216,53 +216,6 @@ class Arvados::V1::SchemaController < ApplicationController } |, 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 diff --git a/services/api/test/fixtures/collections.yml b/services/api/test/fixtures/collections.yml index 6543f5422a..f8d79bd79e 100644 --- a/services/api/test/fixtures/collections.yml +++ b/services/api/test/fixtures/collections.yml @@ -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 diff --git a/services/api/test/functional/arvados/v1/collections_controller_test.rb b/services/api/test/functional/arvados/v1/collections_controller_test.rb index 17af916b3d..70f35f3dd2 100644 --- a/services/api/test/functional/arvados/v1/collections_controller_test.rb +++ b/services/api/test/functional/arvados/v1/collections_controller_test.rb @@ -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 diff --git a/services/ws/config.go b/services/ws/config.go index 79c2f232da..cf82cf8e10 100644 --- a/services/ws/config.go +++ b/services/ws/config.go @@ -17,6 +17,8 @@ type wsConfig struct { PingTimeout arvados.Duration ClientEventQueue int ServerEventQueue int + + ManagementToken string } func defaultConfig() wsConfig { diff --git a/services/ws/event.go b/services/ws/event.go index 304f86bbd0..fd280aebb9 100644 --- a/services/ws/event.go +++ b/services/ws/event.go @@ -17,6 +17,7 @@ type eventSink interface { type eventSource interface { NewSink() eventSink DB() *sql.DB + DBHealth() error } type event struct { diff --git a/services/ws/event_source.go b/services/ws/event_source.go index 7c1b58492d..daf9a94cc1 100644 --- a/services/ws/event_source.go +++ b/services/ws/event_source.go @@ -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(), } } diff --git a/services/ws/event_source_test.go b/services/ws/event_source_test.go index b157cfa0eb..94e3ba3ea0 100644 --- a/services/ws/event_source_test.go +++ b/services/ws/event_source_test.go @@ -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) } diff --git a/services/ws/router.go b/services/ws/router.go index 15b825f2ab..77744974d3 100644 --- a/services/ws/router.go +++ b/services/ws/router.go @@ -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) } - } + }) } diff --git a/services/ws/server_test.go b/services/ws/server_test.go index d74f7dff42..3e19b690b5 100644 --- a/services/ws/server_test.go +++ b/services/ws/server_test.go @@ -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) + } } diff --git a/services/ws/session_v0.go b/services/ws/session_v0.go index 44e2a1deb5..f8645eb887 100644 --- a/services/ws/session_v0.go +++ b/services/ws/session_v0.go @@ -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) diff --git a/services/ws/session_v0_test.go b/services/ws/session_v0_test.go index 85e36560e8..f6fe3f60e6 100644 --- a/services/ws/session_v0_test.go +++ b/services/ws/session_v0_test.go @@ -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)