[ARVADOS] created: 1.3.0-2123-g4d969d35d

Git user git at public.arvados.org
Mon Feb 3 20:29:15 UTC 2020


        at  4d969d35d1e1f022bd5ddcebb2de915bedd01334 (commit)


commit 4d969d35d1e1f022bd5ddcebb2de915bedd01334
Author: Tom Clegg <tom at tomclegg.ca>
Date:   Mon Feb 3 15:28:04 2020 -0500

    16104: Don't impose federated query limitations on local-only query.
    
    Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom at tomclegg.ca>

diff --git a/doc/api/methods.html.textile.liquid b/doc/api/methods.html.textile.liquid
index 175463c32..e08dbb283 100644
--- a/doc/api/methods.html.textile.liquid
+++ b/doc/api/methods.html.textile.liquid
@@ -141,7 +141,6 @@ To query multiple clusters, the list request must:
 
 * Have filters only matching @[["uuid", "in", [...]]@ or @["uuid", "=", "..."]@
 * Specify @count=none@
-* If @select@ is specified, it must include @uuid@
 * Not specify @limit@, @offset@ or @order@
 * Not request more items than the maximum response size
 
diff --git a/lib/controller/federation/list.go b/lib/controller/federation/list.go
index 97c201e74..6ee813317 100644
--- a/lib/controller/federation/list.go
+++ b/lib/controller/federation/list.go
@@ -84,7 +84,7 @@ func (conn *Conn) generated_CollectionList(ctx context.Context, options arvados.
 //
 // * len(Order)==0
 //
-// * Each filter must be either "uuid = ..." or "uuid in [...]".
+// * Each filter is either "uuid = ..." or "uuid in [...]".
 //
 // * The maximum possible response size (total number of objects that
 //   could potentially be matched by all of the specified filters)
@@ -181,28 +181,27 @@ func (conn *Conn) splitListRequest(ctx context.Context, opts arvados.ListOptions
 		}
 	}
 
-	if len(todoByRemote) > 1 {
-		if cannotSplit {
-			return httpErrorf(http.StatusBadRequest, "cannot execute federated list query: each filter must be either 'uuid = ...' or 'uuid in [...]'")
-		}
-		if opts.Count != "none" {
-			return httpErrorf(http.StatusBadRequest, "cannot execute federated list query unless count==\"none\"")
-		}
-		if opts.Limit >= 0 || opts.Offset != 0 || len(opts.Order) > 0 {
-			return httpErrorf(http.StatusBadRequest, "cannot execute federated list query with limit, offset, or order parameter")
-		}
-		if max := conn.cluster.API.MaxItemsPerResponse; nUUIDs > max {
-			return httpErrorf(http.StatusBadRequest, "cannot execute federated list query because number of UUIDs (%d) exceeds page size limit %d", nUUIDs, max)
-		}
-		selectingUUID := false
-		for _, attr := range opts.Select {
-			if attr == "uuid" {
-				selectingUUID = true
-			}
-		}
-		if opts.Select != nil && !selectingUUID {
-			return httpErrorf(http.StatusBadRequest, "cannot execute federated list query with a select parameter that does not include uuid")
-		}
+	if len(todoByRemote) == 0 {
+		return nil
+	}
+	if len(todoByRemote) == 1 && todoByRemote[conn.cluster.ClusterID] != nil {
+		// All UUIDs are local, so proxy a single request. The
+		// generic case has some limitations (see below) which
+		// we don't want to impose on local requests.
+		_, err := fn(ctx, conn.cluster.ClusterID, conn.local, opts)
+		return err
+	}
+	if cannotSplit {
+		return httpErrorf(http.StatusBadRequest, "cannot execute federated list query: each filter must be either 'uuid = ...' or 'uuid in [...]'")
+	}
+	if opts.Count != "none" {
+		return httpErrorf(http.StatusBadRequest, "cannot execute federated list query unless count==\"none\"")
+	}
+	if opts.Limit >= 0 || opts.Offset != 0 || len(opts.Order) > 0 {
+		return httpErrorf(http.StatusBadRequest, "cannot execute federated list query with limit, offset, or order parameter")
+	}
+	if max := conn.cluster.API.MaxItemsPerResponse; nUUIDs > max {
+		return httpErrorf(http.StatusBadRequest, "cannot execute federated list query because number of UUIDs (%d) exceeds page size limit %d", nUUIDs, max)
 	}
 
 	ctx, cancel := context.WithCancel(ctx)
@@ -225,6 +224,12 @@ func (conn *Conn) splitListRequest(ctx context.Context, opts arvados.ListOptions
 				return
 			}
 			remoteOpts := opts
+			if remoteOpts.Select != nil {
+				// We always need to select UUIDs to
+				// use the response, even if our
+				// caller doesn't.
+				remoteOpts.Select = append([]string{"uuid"}, remoteOpts.Select...)
+			}
 			for len(todo) > 0 {
 				if len(batch) > len(todo) {
 					// Reduce batch to just the todo's
diff --git a/lib/controller/federation/list_test.go b/lib/controller/federation/list_test.go
index f61dd5476..ce84378a3 100644
--- a/lib/controller/federation/list_test.go
+++ b/lib/controller/federation/list_test.go
@@ -8,6 +8,7 @@ import (
 	"context"
 	"fmt"
 	"net/http"
+	"reflect"
 	"sort"
 
 	"git.arvados.org/arvados.git/sdk/go/arvados"
@@ -61,6 +62,13 @@ func (cl *collectionLister) CollectionList(ctx context.Context, options arvados.
 			break
 		}
 		if cl.matchFilters(c, options.Filters) {
+			if reflect.DeepEqual(options.Select, []string{"uuid", "name"}) {
+				c = arvados.Collection{UUID: c.UUID, Name: c.Name}
+			} else if reflect.DeepEqual(options.Select, []string{"name"}) {
+				c = arvados.Collection{Name: c.Name}
+			} else if len(options.Select) > 0 {
+				panic(fmt.Sprintf("not implemented: options=%#v", options))
+			}
 			resp.Items = append(resp.Items, c)
 		}
 	}
@@ -111,6 +119,7 @@ type listTrial struct {
 	offset       int
 	order        []string
 	filters      []arvados.Filter
+	selectfields []string
 	expectUUIDs  []string
 	expectCalls  []int // number of API calls to backends
 	expectStatus int
@@ -145,6 +154,17 @@ func (s *CollectionListSuite) TestCollectionListOneRemote(c *check.C) {
 	})
 }
 
+func (s *CollectionListSuite) TestCollectionListOneLocalDeselectingUUID(c *check.C) {
+	s.test(c, listTrial{
+		count:        "none",
+		limit:        -1,
+		filters:      []arvados.Filter{{"uuid", "=", s.uuids[0][0]}},
+		selectfields: []string{"name"},
+		expectUUIDs:  []string{""}, // select=name is honored
+		expectCalls:  []int{1, 0, 0},
+	})
+}
+
 func (s *CollectionListSuite) TestCollectionListOneLocalUsingInOperator(c *check.C) {
 	s.test(c, listTrial{
 		count:       "none",
@@ -165,6 +185,17 @@ func (s *CollectionListSuite) TestCollectionListOneRemoteUsingInOperator(c *chec
 	})
 }
 
+func (s *CollectionListSuite) TestCollectionListOneRemoteDeselectingUUID(c *check.C) {
+	s.test(c, listTrial{
+		count:        "none",
+		limit:        -1,
+		filters:      []arvados.Filter{{"uuid", "=", s.uuids[1][0]}},
+		selectfields: []string{"name"},
+		expectUUIDs:  []string{s.uuids[1][0]}, // uuid is returned, despite not being selected
+		expectCalls:  []int{0, 1, 0},
+	})
+}
+
 func (s *CollectionListSuite) TestCollectionListOneLocalOneRemote(c *check.C) {
 	s.test(c, listTrial{
 		count:       "none",
@@ -175,6 +206,17 @@ func (s *CollectionListSuite) TestCollectionListOneLocalOneRemote(c *check.C) {
 	})
 }
 
+func (s *CollectionListSuite) TestCollectionListOneLocalOneRemoteDeselectingUUID(c *check.C) {
+	s.test(c, listTrial{
+		count:        "none",
+		limit:        -1,
+		filters:      []arvados.Filter{{"uuid", "in", []string{s.uuids[0][0], s.uuids[1][0]}}},
+		selectfields: []string{"name"},
+		expectUUIDs:  []string{s.uuids[0][0], s.uuids[1][0]}, // uuid is returned, despite not being selected
+		expectCalls:  []int{1, 1, 0},
+	})
+}
+
 func (s *CollectionListSuite) TestCollectionListTwoRemotes(c *check.C) {
 	s.test(c, listTrial{
 		count:       "none",
@@ -356,6 +398,7 @@ func (s *CollectionListSuite) test(c *check.C, trial listTrial) {
 		Offset:  trial.offset,
 		Order:   trial.order,
 		Filters: trial.filters,
+		Select:  trial.selectfields,
 	})
 	if trial.expectStatus != 0 {
 		c.Assert(err, check.NotNil)

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list