1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
16 "git.curoverse.com/arvados.git/lib/controller/router"
17 "git.curoverse.com/arvados.git/lib/controller/rpc"
18 "git.curoverse.com/arvados.git/sdk/go/arvados"
19 "git.curoverse.com/arvados.git/sdk/go/arvadostest"
20 "git.curoverse.com/arvados.git/sdk/go/auth"
21 "git.curoverse.com/arvados.git/sdk/go/ctxlog"
22 "git.curoverse.com/arvados.git/sdk/go/httpserver"
23 check "gopkg.in/check.v1"
26 // Gocheck boilerplate
27 func Test(t *testing.T) {
32 _ = check.Suite(&FederationSuite{})
33 _ = check.Suite(&CollectionListSuite{})
36 type FederationSuite struct {
37 cluster *arvados.Cluster
42 func (s *FederationSuite) SetUpTest(c *check.C) {
43 s.cluster = &arvados.Cluster{
45 RemoteClusters: map[string]arvados.RemoteCluster{
46 "aaaaa": arvados.RemoteCluster{
47 Host: os.Getenv("ARVADOS_API_HOST"),
51 arvadostest.SetServiceURL(&s.cluster.Services.RailsAPI, "https://"+os.Getenv("ARVADOS_TEST_API_HOST"))
52 s.cluster.TLS.Insecure = true
53 s.cluster.API.MaxItemsPerResponse = 3
55 ctx := context.Background()
56 ctx = ctxlog.Context(ctx, ctxlog.TestLogger(c))
57 ctx = auth.NewContext(ctx, &auth.Credentials{Tokens: []string{arvadostest.ActiveTokenV2}})
60 s.fed = New(s.cluster)
63 func (s *FederationSuite) addDirectRemote(c *check.C, id string, backend backend) {
64 s.cluster.RemoteClusters[id] = arvados.RemoteCluster{
65 Host: "in-process.local",
67 s.fed.remotes[id] = backend
70 func (s *FederationSuite) addHTTPRemote(c *check.C, id string, backend backend) {
71 srv := httpserver.Server{Addr: ":"}
72 srv.Handler = router.New(backend)
73 c.Check(srv.Start(), check.IsNil)
74 s.cluster.RemoteClusters[id] = arvados.RemoteCluster{
78 s.fed.remotes[id] = rpc.NewConn(id, &url.URL{Scheme: "http", Host: srv.Addr}, true, saltedTokenProvider(s.fed.local, id))
81 type collectionLister struct {
83 ItemsToReturn []arvados.Collection
87 func (cl *collectionLister) matchFilters(c arvados.Collection, filters []arvados.Filter) bool {
89 for _, f := range filters {
90 if f.Attr == "uuid" && f.Operator == "=" {
91 s, ok := f.Operand.(string)
92 if ok && s == c.UUID {
95 } else if f.Attr == "uuid" && f.Operator == "in" {
96 if operand, ok := f.Operand.([]string); ok {
97 for _, s := range operand {
102 } else if operand, ok := f.Operand.([]interface{}); ok {
103 for _, s := range operand {
104 if s, ok := s.(string); ok && s == c.UUID {
115 func (cl *collectionLister) CollectionList(ctx context.Context, options arvados.ListOptions) (resp arvados.CollectionList, _ error) {
116 cl.APIStub.CollectionList(ctx, options)
117 for _, c := range cl.ItemsToReturn {
118 if cl.MaxPageSize > 0 && len(resp.Items) >= cl.MaxPageSize {
121 if options.Limit >= 0 && len(resp.Items) >= options.Limit {
124 if cl.matchFilters(c, options.Filters) {
125 resp.Items = append(resp.Items, c)
131 type CollectionListSuite struct {
133 ids []string // aaaaa, bbbbb, ccccc
134 uuids [][]string // [[aa-*, aa-*, aa-*], [bb-*, bb-*, ...], ...]
135 backends []*collectionLister
138 func (s *CollectionListSuite) SetUpTest(c *check.C) {
139 s.FederationSuite.SetUpTest(c)
144 for i, id := range []string{"aaaaa", "bbbbb", "ccccc"} {
145 cl := &collectionLister{}
146 s.ids = append(s.ids, id)
147 s.uuids = append(s.uuids, nil)
148 for j := 0; j < 5; j++ {
149 uuid := fmt.Sprintf("%s-4zz18-%s%010d", id, id, j)
150 s.uuids[i] = append(s.uuids[i], uuid)
151 cl.ItemsToReturn = append(cl.ItemsToReturn, arvados.Collection{
155 s.backends = append(s.backends, cl)
159 // call some backends directly via API
160 s.addDirectRemote(c, id, cl)
162 // call some backends through rpc->router->API
163 // to ensure nothing is lost in translation
164 s.addHTTPRemote(c, id, cl)
169 type listTrial struct {
174 filters []arvados.Filter
176 expectCalls []int // number of API calls to backends
180 func (s *CollectionListSuite) TestCollectionListNoUUIDFilters(c *check.C) {
184 expectUUIDs: []string{s.uuids[0][0]},
185 expectCalls: []int{1, 0, 0},
189 func (s *CollectionListSuite) TestCollectionListOneLocal(c *check.C) {
193 filters: []arvados.Filter{{"uuid", "=", s.uuids[0][0]}},
194 expectUUIDs: []string{s.uuids[0][0]},
195 expectCalls: []int{1, 0, 0},
199 func (s *CollectionListSuite) TestCollectionListOneRemote(c *check.C) {
203 filters: []arvados.Filter{{"uuid", "=", s.uuids[1][0]}},
204 expectUUIDs: []string{s.uuids[1][0]},
205 expectCalls: []int{0, 1, 0},
209 func (s *CollectionListSuite) TestCollectionListOneLocalUsingInOperator(c *check.C) {
213 filters: []arvados.Filter{{"uuid", "in", []string{s.uuids[0][0]}}},
214 expectUUIDs: []string{s.uuids[0][0]},
215 expectCalls: []int{1, 0, 0},
219 func (s *CollectionListSuite) TestCollectionListOneRemoteUsingInOperator(c *check.C) {
223 filters: []arvados.Filter{{"uuid", "in", []string{s.uuids[1][1]}}},
224 expectUUIDs: []string{s.uuids[1][1]},
225 expectCalls: []int{0, 1, 0},
229 func (s *CollectionListSuite) TestCollectionListOneLocalOneRemote(c *check.C) {
233 filters: []arvados.Filter{{"uuid", "in", []string{s.uuids[0][0], s.uuids[1][0]}}},
234 expectUUIDs: []string{s.uuids[0][0], s.uuids[1][0]},
235 expectCalls: []int{1, 1, 0},
239 func (s *CollectionListSuite) TestCollectionListTwoRemotes(c *check.C) {
243 filters: []arvados.Filter{{"uuid", "in", []string{s.uuids[2][0], s.uuids[1][0]}}},
244 expectUUIDs: []string{s.uuids[1][0], s.uuids[2][0]},
245 expectCalls: []int{0, 1, 1},
249 func (s *CollectionListSuite) TestCollectionListSatisfyAllFilters(c *check.C) {
250 s.cluster.API.MaxItemsPerResponse = 2
254 filters: []arvados.Filter{
255 {"uuid", "in", []string{s.uuids[0][0], s.uuids[1][1], s.uuids[2][0], s.uuids[2][1], s.uuids[2][2]}},
256 {"uuid", "in", []string{s.uuids[0][0], s.uuids[1][2], s.uuids[2][1]}},
258 expectUUIDs: []string{s.uuids[0][0], s.uuids[2][1]},
259 expectCalls: []int{1, 0, 1},
263 func (s *CollectionListSuite) TestCollectionListEmptySet(c *check.C) {
267 filters: []arvados.Filter{{"uuid", "in", []string{}}},
268 expectUUIDs: []string{},
269 expectCalls: []int{0, 0, 0},
273 func (s *CollectionListSuite) TestCollectionListUnmatchableUUID(c *check.C) {
277 filters: []arvados.Filter{
278 {"uuid", "in", []string{s.uuids[0][0], "abcdefg"}},
279 {"uuid", "in", []string{s.uuids[0][0], "bbbbb-4zz18-bogus"}},
280 {"uuid", "in", []string{s.uuids[0][0], "bogus-4zz18-bogus"}},
282 expectUUIDs: []string{s.uuids[0][0]},
283 expectCalls: []int{1, 0, 0},
287 func (s *CollectionListSuite) TestCollectionListMultiPage(c *check.C) {
288 for i := range s.backends {
289 s.uuids[i] = s.uuids[i][:3]
290 s.backends[i].ItemsToReturn = s.backends[i].ItemsToReturn[:3]
292 s.cluster.API.MaxItemsPerResponse = 9
293 for _, stub := range s.backends {
296 allUUIDs := append(append(append([]string(nil), s.uuids[0]...), s.uuids[1]...), s.uuids[2]...)
300 filters: []arvados.Filter{{"uuid", "in", append([]string(nil), allUUIDs...)}},
301 expectUUIDs: allUUIDs,
302 expectCalls: []int{2, 2, 2},
306 func (s *CollectionListSuite) TestCollectionListMultiSiteExtraFilters(c *check.C) {
307 // not [yet] supported
311 filters: []arvados.Filter{
312 {"uuid", "in", []string{s.uuids[0][0], s.uuids[1][0]}},
313 {"uuid", "is_a", "teapot"},
315 expectCalls: []int{0, 0, 0},
316 expectStatus: http.StatusBadRequest,
320 func (s *CollectionListSuite) TestCollectionListMultiSiteWithCount(c *check.C) {
321 for _, count := range []string{"", "exact"} {
325 filters: []arvados.Filter{
326 {"uuid", "in", []string{s.uuids[0][0], s.uuids[1][0]}},
327 {"uuid", "is_a", "teapot"},
329 expectCalls: []int{0, 0, 0},
330 expectStatus: http.StatusBadRequest,
335 func (s *CollectionListSuite) TestCollectionListMultiSiteWithLimit(c *check.C) {
336 for _, limit := range []int{0, 1, 2} {
340 filters: []arvados.Filter{
341 {"uuid", "in", []string{s.uuids[0][0], s.uuids[1][0]}},
342 {"uuid", "is_a", "teapot"},
344 expectCalls: []int{0, 0, 0},
345 expectStatus: http.StatusBadRequest,
350 func (s *CollectionListSuite) TestCollectionListMultiSiteWithOffset(c *check.C) {
355 filters: []arvados.Filter{
356 {"uuid", "in", []string{s.uuids[0][0], s.uuids[1][0]}},
357 {"uuid", "is_a", "teapot"},
359 expectCalls: []int{0, 0, 0},
360 expectStatus: http.StatusBadRequest,
364 func (s *CollectionListSuite) TestCollectionListMultiSiteWithOrder(c *check.C) {
368 order: []string{"uuid desc"},
369 filters: []arvados.Filter{
370 {"uuid", "in", []string{s.uuids[0][0], s.uuids[1][0]}},
371 {"uuid", "is_a", "teapot"},
373 expectCalls: []int{0, 0, 0},
374 expectStatus: http.StatusBadRequest,
378 func (s *CollectionListSuite) TestCollectionListInvalidFilters(c *check.C) {
382 filters: []arvados.Filter{
383 {"uuid", "in", "teapot"},
385 expectCalls: []int{0, 0, 0},
386 expectStatus: http.StatusBadRequest,
390 func (s *CollectionListSuite) TestCollectionListRemoteUnknown(c *check.C) {
394 filters: []arvados.Filter{
395 {"uuid", "in", []string{s.uuids[0][0], "bogus-4zz18-000001111122222"}},
397 expectStatus: http.StatusNotFound,
401 func (s *CollectionListSuite) TestCollectionListRemoteError(c *check.C) {
402 s.addDirectRemote(c, "bbbbb", &arvadostest.APIStub{})
406 filters: []arvados.Filter{
407 {"uuid", "in", []string{s.uuids[0][0], s.uuids[1][0]}},
409 expectStatus: http.StatusBadGateway,
413 func (s *CollectionListSuite) test(c *check.C, trial listTrial) {
414 resp, err := s.fed.CollectionList(s.ctx, arvados.ListOptions{
417 Offset: trial.offset,
419 Filters: trial.filters,
421 if trial.expectStatus != 0 {
422 c.Assert(err, check.NotNil)
423 err, _ := err.(interface{ HTTPStatus() int })
424 c.Assert(err, check.NotNil) // err must implement HTTPStatus()
425 c.Check(err.HTTPStatus(), check.Equals, trial.expectStatus)
426 c.Logf("returned error is %#v", err)
427 c.Logf("returned error string is %q", err)
429 c.Check(err, check.IsNil)
430 expectItems := []arvados.Collection{}
431 for _, uuid := range trial.expectUUIDs {
432 expectItems = append(expectItems, arvados.Collection{UUID: uuid})
434 // expectItems is sorted by UUID, so sort resp.Items
435 // by UUID before checking DeepEquals.
436 sort.Slice(resp.Items, func(i, j int) bool { return resp.Items[i].UUID < resp.Items[j].UUID })
437 c.Check(resp, check.DeepEquals, arvados.CollectionList{
442 for i, stub := range s.backends {
443 if i >= len(trial.expectCalls) {
446 calls := stub.Calls(nil)
447 c.Check(calls, check.HasLen, trial.expectCalls[i])
451 opts := calls[0].Options.(arvados.ListOptions)
452 c.Check(opts.Limit, check.Equals, trial.limit)