[ARVADOS] created: 1.2.0-106-ge9a3b4476

Git user git at public.curoverse.com
Thu Sep 27 12:30:32 EDT 2018


        at  e9a3b4476759582e49404a0a1f8e820ad5d97fcd (commit)


commit e9a3b4476759582e49404a0a1f8e820ad5d97fcd
Author: Peter Amstutz <pamstutz at veritasgenetics.com>
Date:   Thu Sep 27 12:29:35 2018 -0400

    13619: MultiClusterQuery passes test
    
    Arvados-DCO-1.1-Signed-off-by: Peter Amstutz <pamstutz at veritasgenetics.com>

diff --git a/lib/controller/federation.go b/lib/controller/federation.go
index dc03e039f..caa84ca5f 100644
--- a/lib/controller/federation.go
+++ b/lib/controller/federation.go
@@ -14,7 +14,6 @@ import (
 	"fmt"
 	"io"
 	"io/ioutil"
-	"log"
 	"net/http"
 	"net/url"
 	"regexp"
@@ -129,7 +128,11 @@ func (c *responseCollector) collectResponse(resp *http.Response, requestError er
 	defer c.mtx.Unlock()
 
 	if err == nil {
-		c.responses = append(c.responses, loadInto["items"].([]interface{})...)
+		if resp.StatusCode != http.StatusOK {
+			c.errors = append(c.errors, fmt.Errorf("error %v", loadInto["errors"]))
+		} else {
+			c.responses = append(c.responses, loadInto["items"].([]interface{})...)
+		}
 	} else {
 		c.errors = append(c.errors, err)
 	}
@@ -186,19 +189,17 @@ func (h *genericFederatedRequestHandler) handleMultiClusterQuery(w http.Response
 				wg.Done()
 				<-sem
 			}()
-			remoteReq := *req
+			var remoteReq http.Request
+			remoteReq.Header = req.Header
 			remoteReq.Method = "POST"
-			remoteReq.URL = &url.URL{
-				Path:    req.URL.Path,
-				RawPath: req.URL.RawPath,
-			}
+			remoteReq.URL = &url.URL{Path: req.URL.Path}
 			remoteParams := make(url.Values)
 			remoteParams["_method"] = []string{"GET"}
 			content, err := json.Marshal(v)
 			if err != nil {
 				rc.mtx.Lock()
+				defer rc.mtx.Unlock()
 				rc.errors = append(rc.errors, err)
-				rc.mtx.Unlock()
 				return
 			}
 			remoteParams["filters"] = []string{fmt.Sprintf(`[["uuid", "in", %s]]`, content)}
@@ -206,8 +207,8 @@ func (h *genericFederatedRequestHandler) handleMultiClusterQuery(w http.Response
 			remoteReq.Body = ioutil.NopCloser(bytes.NewBufferString(enc))
 
 			if k == h.handler.Cluster.ClusterID {
-				h.handler.proxy.Do(w, &remoteReq, remoteReq.URL,
-					h.handler.secureClient, rc.collectResponse)
+				h.handler.localClusterRequest(w, &remoteReq,
+					rc.collectResponse)
 			} else {
 				h.handler.remoteClusterRequest(k, w, &remoteReq,
 					rc.collectResponse)
@@ -222,17 +223,13 @@ func (h *genericFederatedRequestHandler) handleMultiClusterQuery(w http.Response
 		for _, e := range rc.errors {
 			strerr = append(strerr, e.Error())
 		}
-		httpserver.Errors(w, strerr, http.StatusBadRequest)
+		httpserver.Errors(w, strerr, http.StatusBadGateway)
 	} else {
-		log.Printf("Sending status ok %+v", rc)
 		w.Header().Set("Content-Type", "application/json")
 		w.WriteHeader(http.StatusOK)
 		itemList := make(map[string]interface{})
 		itemList["items"] = rc.responses
-		//x, _ := json.Marshal(itemList)
-		//log.Printf("Sending response %v", string(x))
 		json.NewEncoder(w).Encode(itemList)
-		log.Printf("Sent?")
 	}
 
 	return true
@@ -285,7 +282,7 @@ func (h *genericFederatedRequestHandler) ServeHTTP(w http.ResponseWriter, req *h
 			return
 		}
 	}
-	log.Printf("Clusterid is %q", clusterId)
+	//log.Printf("Clusterid is %q", clusterId)
 
 	if clusterId == "" || clusterId == h.handler.Cluster.ClusterID {
 		h.next.ServeHTTP(w, req)
@@ -405,11 +402,7 @@ func (rw rewriteSignaturesClusterId) rewriteSignatures(resp *http.Response, requ
 	return resp, nil
 }
 
-type searchLocalClusterForPDH struct {
-	sentResponse bool
-}
-
-func (s *searchLocalClusterForPDH) filterLocalClusterResponse(resp *http.Response, requestError error) (newResponse *http.Response, err error) {
+func filterLocalClusterResponse(resp *http.Response, requestError error) (newResponse *http.Response, err error) {
 	if requestError != nil {
 		return resp, requestError
 	}
@@ -417,10 +410,8 @@ func (s *searchLocalClusterForPDH) filterLocalClusterResponse(resp *http.Respons
 	if resp.StatusCode == 404 {
 		// Suppress returning this result, because we want to
 		// search the federation.
-		s.sentResponse = false
 		return nil, nil
 	}
-	s.sentResponse = true
 	return resp, nil
 }
 
@@ -526,26 +517,7 @@ func (h *collectionFederatedRequestHandler) ServeHTTP(w http.ResponseWriter, req
 	// Request for collection by PDH.  Search the federation.
 
 	// First, query the local cluster.
-	urlOut, insecure, err := findRailsAPI(h.handler.Cluster, h.handler.NodeProfile)
-	if err != nil {
-		httpserver.Error(w, err.Error(), http.StatusInternalServerError)
-		return
-	}
-
-	urlOut = &url.URL{
-		Scheme:   urlOut.Scheme,
-		Host:     urlOut.Host,
-		Path:     req.URL.Path,
-		RawPath:  req.URL.RawPath,
-		RawQuery: req.URL.RawQuery,
-	}
-	client := h.handler.secureClient
-	if insecure {
-		client = h.handler.insecureClient
-	}
-	sf := &searchLocalClusterForPDH{}
-	h.handler.proxy.Do(w, req, urlOut, client, sf.filterLocalClusterResponse)
-	if sf.sentResponse {
+	if h.handler.localClusterRequest(w, req, filterLocalClusterResponse) {
 		return
 	}
 
diff --git a/lib/controller/federation_test.go b/lib/controller/federation_test.go
index b94efa6ae..113fa9eeb 100644
--- a/lib/controller/federation_test.go
+++ b/lib/controller/federation_test.go
@@ -8,7 +8,6 @@ import (
 	"encoding/json"
 	"fmt"
 	"io/ioutil"
-	"log"
 	"net/http"
 	"net/http/httptest"
 	"net/url"
@@ -304,12 +303,10 @@ func (s *FederationSuite) checkJSONErrorMatches(c *check.C, resp *http.Response,
 	c.Check(jresp.Errors[0], check.Matches, re)
 }
 
-func (s *FederationSuite) localServiceReturns404(c *check.C) *httpserver.Server {
+func (s *FederationSuite) localServiceHandler(c *check.C, h http.Handler) *httpserver.Server {
 	srv := &httpserver.Server{
 		Server: http.Server{
-			Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
-				w.WriteHeader(404)
-			}),
+			Handler: h,
 		},
 	}
 
@@ -325,6 +322,12 @@ func (s *FederationSuite) localServiceReturns404(c *check.C) *httpserver.Server
 	return srv
 }
 
+func (s *FederationSuite) localServiceReturns404(c *check.C) *httpserver.Server {
+	return s.localServiceHandler(c, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+		w.WriteHeader(404)
+	}))
+}
+
 func (s *FederationSuite) TestGetLocalCollection(c *check.C) {
 	np := arvados.NodeProfile{
 		Controller: arvados.SystemServiceInstance{Listen: ":"},
@@ -629,14 +632,23 @@ func (s *FederationSuite) TestListRemoteContainer(c *check.C) {
 }
 
 func (s *FederationSuite) TestListMultiRemoteContainers(c *check.C) {
-	defer s.localServiceReturns404(c).Close()
+	defer s.localServiceHandler(c, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+		bd, _ := ioutil.ReadAll(req.Body)
+		c.Check(string(bd), check.Equals, `_method=GET&filters=%5B%5B%22uuid%22%2C+%22in%22%2C+%5B%22zhome-xvhdp-cr5queuedcontnr%22%5D%5D%5D`)
+		w.WriteHeader(200)
+		w.Write([]byte(`{"items": [{"uuid": "zhome-xvhdp-cr5queuedcontnr"}]}`))
+	})).Close()
 	req := httptest.NewRequest("GET", "/arvados/v1/containers?filters="+
 		url.QueryEscape(fmt.Sprintf(`[["uuid", "in", ["%v", "zhome-xvhdp-cr5queuedcontnr"]]]`, arvadostest.QueuedContainerUUID)), nil)
 	req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
 	resp := s.testRequest(req)
-	log.Printf("got %+v", resp)
-	c.Assert(resp.StatusCode, check.Equals, http.StatusOK)
+	c.Check(resp.StatusCode, check.Equals, http.StatusOK)
 	var cn arvados.ContainerList
 	c.Check(json.NewDecoder(resp.Body).Decode(&cn), check.IsNil)
-	c.Check(cn.Items[0].UUID, check.Equals, arvadostest.QueuedContainerUUID)
+	if cn.Items[0].UUID == arvadostest.QueuedContainerUUID {
+		c.Check(cn.Items[1].UUID, check.Equals, "zhome-xvhdp-cr5queuedcontnr")
+	} else {
+		c.Check(cn.Items[1].UUID, check.Equals, arvadostest.QueuedContainerUUID)
+		c.Check(cn.Items[0].UUID, check.Equals, "zhome-xvhdp-cr5queuedcontnr")
+	}
 }
diff --git a/lib/controller/handler.go b/lib/controller/handler.go
index 2b41aba6b..0c31815cb 100644
--- a/lib/controller/handler.go
+++ b/lib/controller/handler.go
@@ -121,11 +121,14 @@ func prepend(next http.Handler, middleware middlewareFunc) http.Handler {
 	})
 }
 
-func (h *Handler) proxyRailsAPI(w http.ResponseWriter, req *http.Request, next http.Handler) {
+// localClusterRequest sets up a request so it can be proxied to the
+// local API server using proxy.Do().  Returns true if a response was
+// written, false if not.
+func (h *Handler) localClusterRequest(w http.ResponseWriter, req *http.Request, filter ResponseFilter) bool {
 	urlOut, insecure, err := findRailsAPI(h.Cluster, h.NodeProfile)
 	if err != nil {
 		httpserver.Error(w, err.Error(), http.StatusInternalServerError)
-		return
+		return true
 	}
 	urlOut = &url.URL{
 		Scheme:   urlOut.Scheme,
@@ -138,7 +141,13 @@ func (h *Handler) proxyRailsAPI(w http.ResponseWriter, req *http.Request, next h
 	if insecure {
 		client = h.insecureClient
 	}
-	h.proxy.Do(w, req, urlOut, client, nil)
+	return h.proxy.Do(w, req, urlOut, client, filter)
+}
+
+func (h *Handler) proxyRailsAPI(w http.ResponseWriter, req *http.Request, next http.Handler) {
+	if !h.localClusterRequest(w, req, nil) && next != nil {
+		next.ServeHTTP(w, req)
+	}
 }
 
 // For now, findRailsAPI always uses the rails API running on this
diff --git a/lib/controller/proxy.go b/lib/controller/proxy.go
index 373b42e8f..951cb9d25 100644
--- a/lib/controller/proxy.go
+++ b/lib/controller/proxy.go
@@ -36,11 +36,15 @@ var dropHeaders = map[string]bool{
 
 type ResponseFilter func(*http.Response, error) (*http.Response, error)
 
+// Do sends a request, passes the result to the filter (if provided)
+// and then if the result is not suppressed by the filter, sends the
+// request to the ResponseWriter.  Returns true if a response was written,
+// false if not.
 func (p *proxy) Do(w http.ResponseWriter,
 	reqIn *http.Request,
 	urlOut *url.URL,
 	client *http.Client,
-	filter ResponseFilter) {
+	filter ResponseFilter) bool {
 
 	// Copy headers from incoming request, then add/replace proxy
 	// headers like Via and X-Forwarded-For.
@@ -78,7 +82,7 @@ func (p *proxy) Do(w http.ResponseWriter,
 	resp, err := client.Do(reqOut)
 	if filter == nil && err != nil {
 		httpserver.Error(w, err.Error(), http.StatusBadGateway)
-		return
+		return true
 	}
 
 	// make sure original response body gets closed
@@ -95,13 +99,13 @@ func (p *proxy) Do(w http.ResponseWriter,
 
 		if err != nil {
 			httpserver.Error(w, err.Error(), http.StatusBadGateway)
-			return
+			return true
 		}
 		if resp == nil {
 			// filter() returned a nil response, this means suppress
 			// writing a response, for the case where there might
 			// be multiple response writers.
-			return
+			return false
 		}
 
 		// the filter gave us a new response body, make sure that gets closed too.
@@ -120,4 +124,5 @@ func (p *proxy) Do(w http.ResponseWriter,
 	if err != nil {
 		httpserver.Logger(reqIn).WithError(err).WithField("bytesCopied", n).Error("error copying response body")
 	}
+	return true
 }

commit 342ec439e7965d889a7aca306475424d3a3ff986
Author: Peter Amstutz <pamstutz at veritasgenetics.com>
Date:   Thu Sep 27 10:53:22 2018 -0400

    13619: Federated multi-object list wip
    
    Arvados-DCO-1.1-Signed-off-by: Peter Amstutz <pamstutz at veritasgenetics.com>

diff --git a/lib/controller/federation.go b/lib/controller/federation.go
index cc22a674d..dc03e039f 100644
--- a/lib/controller/federation.go
+++ b/lib/controller/federation.go
@@ -14,6 +14,7 @@ import (
 	"fmt"
 	"io"
 	"io/ioutil"
+	"log"
 	"net/http"
 	"net/url"
 	"regexp"
@@ -26,7 +27,7 @@ import (
 	"git.curoverse.com/arvados.git/sdk/go/keepclient"
 )
 
-var pathPattern = `^/arvados/v1/%s(/([0-9a-z]{5})-%s-)?.*$`
+var pathPattern = `^/arvados/v1/%s(/([0-9a-z]{5})-%s-[0-9a-z]{15})?(.*)$`
 var wfRe = regexp.MustCompile(fmt.Sprintf(pathPattern, "workflows", "7fd4e"))
 var containersRe = regexp.MustCompile(fmt.Sprintf(pathPattern, "containers", "dz642"))
 var containerRequestsRe = regexp.MustCompile(fmt.Sprintf(pathPattern, "container_requests", "xvhdp"))
@@ -73,44 +74,218 @@ func (h *Handler) remoteClusterRequest(remoteID string, w http.ResponseWriter, r
 	h.proxy.Do(w, req, urlOut, client, filter)
 }
 
+func loadParamsFromForm(req *http.Request, params url.Values) error {
+	body, err := ioutil.ReadAll(req.Body)
+	if err != nil {
+		return err
+	}
+	req.Body = ioutil.NopCloser(bytes.NewBuffer(body))
+	var v2 url.Values
+	if v2, err = url.ParseQuery(string(body)); err != nil {
+		return err
+	}
+	for k, v := range v2 {
+		params[k] = append(params[k], v...)
+	}
+	return nil
+}
+
+func loadParamsFromJson(req *http.Request, loadInto interface{}) error {
+	var cl int64
+	if req.ContentLength > 0 {
+		cl = req.ContentLength
+	}
+	postBody := bytes.NewBuffer(make([]byte, 0, cl))
+	defer req.Body.Close()
+
+	rdr := io.TeeReader(req.Body, postBody)
+
+	err := json.NewDecoder(rdr).Decode(loadInto)
+	if err != nil {
+		return err
+	}
+	req.Body = ioutil.NopCloser(postBody)
+	return nil
+}
+
+type responseCollector struct {
+	mtx       sync.Mutex
+	responses []interface{}
+	errors    []error
+}
+
+func (c *responseCollector) collectResponse(resp *http.Response, requestError error) (newResponse *http.Response, err error) {
+	if requestError != nil {
+		c.mtx.Lock()
+		defer c.mtx.Unlock()
+		c.errors = append(c.errors, requestError)
+		return nil, nil
+	}
+	defer resp.Body.Close()
+	loadInto := make(map[string]interface{})
+	err = json.NewDecoder(resp.Body).Decode(&loadInto)
+
+	c.mtx.Lock()
+	defer c.mtx.Unlock()
+
+	if err == nil {
+		c.responses = append(c.responses, loadInto["items"].([]interface{})...)
+	} else {
+		c.errors = append(c.errors, err)
+	}
+
+	return nil, nil
+}
+
+func (h *genericFederatedRequestHandler) handleMultiClusterQuery(w http.ResponseWriter, req *http.Request,
+	params url.Values, clusterId *string) bool {
+
+	var filters [][]interface{}
+	err := json.Unmarshal([]byte(params["filters"][0]), &filters)
+	if err != nil {
+		httpserver.Error(w, err.Error(), http.StatusBadRequest)
+		return true
+	}
+	queryClusters := make(map[string][]string)
+	if len(filters) == 1 && len(filters[0]) == 3 {
+		f1 := filters[0]
+		lhs := f1[0].(string)
+		op := f1[1].(string)
+		rhs := f1[2].([]interface{})
+		if lhs == "uuid" && op == "in" {
+			for _, i := range rhs {
+				u := i.(string)
+				*clusterId = u[0:5]
+				queryClusters[u[0:5]] = append(queryClusters[u[0:5]], u)
+			}
+		}
+	}
+
+	if len(queryClusters) <= 1 {
+		return false
+	}
+
+	wg := sync.WaitGroup{}
+	//var errors []string
+	//var errorCode int = 404
+
+	// use channel as a semaphore to limit it to 4
+	// parallel requests at a time
+	sem := make(chan bool, 4)
+	defer close(sem)
+	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+
+	rc := responseCollector{}
+	for k, v := range queryClusters {
+		// blocks until it can put a value into the
+		// channel (which has a max queue capacity)
+		sem <- true
+		wg.Add(1)
+		go func(k string, v []string) {
+			defer func() {
+				wg.Done()
+				<-sem
+			}()
+			remoteReq := *req
+			remoteReq.Method = "POST"
+			remoteReq.URL = &url.URL{
+				Path:    req.URL.Path,
+				RawPath: req.URL.RawPath,
+			}
+			remoteParams := make(url.Values)
+			remoteParams["_method"] = []string{"GET"}
+			content, err := json.Marshal(v)
+			if err != nil {
+				rc.mtx.Lock()
+				rc.errors = append(rc.errors, err)
+				rc.mtx.Unlock()
+				return
+			}
+			remoteParams["filters"] = []string{fmt.Sprintf(`[["uuid", "in", %s]]`, content)}
+			enc := remoteParams.Encode()
+			remoteReq.Body = ioutil.NopCloser(bytes.NewBufferString(enc))
+
+			if k == h.handler.Cluster.ClusterID {
+				h.handler.proxy.Do(w, &remoteReq, remoteReq.URL,
+					h.handler.secureClient, rc.collectResponse)
+			} else {
+				h.handler.remoteClusterRequest(k, w, &remoteReq,
+					rc.collectResponse)
+			}
+		}(k, v)
+	}
+	wg.Wait()
+
+	if len(rc.errors) > 0 {
+		// parallel query
+		var strerr []string
+		for _, e := range rc.errors {
+			strerr = append(strerr, e.Error())
+		}
+		httpserver.Errors(w, strerr, http.StatusBadRequest)
+	} else {
+		log.Printf("Sending status ok %+v", rc)
+		w.Header().Set("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+		itemList := make(map[string]interface{})
+		itemList["items"] = rc.responses
+		//x, _ := json.Marshal(itemList)
+		//log.Printf("Sending response %v", string(x))
+		json.NewEncoder(w).Encode(itemList)
+		log.Printf("Sent?")
+	}
+
+	return true
+}
+
 func (h *genericFederatedRequestHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 	m := h.matcher.FindStringSubmatch(req.URL.Path)
 	clusterId := ""
 
-	if len(m) == 3 {
+	if len(m) > 0 && m[2] != "" {
 		clusterId = m[2]
 	}
 
-	if clusterId == "" {
-		if values, err := url.ParseQuery(req.URL.RawQuery); err == nil {
-			if len(values["cluster_id"]) == 1 {
-				clusterId = values["cluster_id"][0]
-			}
+	var params url.Values
+	var err error
+	if params, err = url.ParseQuery(req.URL.RawQuery); err != nil {
+		httpserver.Error(w, err.Error(), http.StatusBadRequest)
+		return
+	}
+
+	if req.Method == "POST" && req.Header.Get("Content-Type") == "application/x-www-form-urlencoded" {
+		if err = loadParamsFromForm(req, params); err != nil {
+			httpserver.Error(w, err.Error(), http.StatusBadRequest)
+			return
 		}
 	}
 
+	if len(params["cluster_id"]) == 1 {
+		clusterId = params["cluster_id"][0]
+	}
+
 	if clusterId == "" && req.Method == "POST" && req.Header.Get("Content-Type") == "application/json" {
 		var hasClusterId struct {
 			ClusterID string `json:"cluster_id"`
 		}
-		var cl int64
-		if req.ContentLength > 0 {
-			cl = req.ContentLength
+		if err = loadParamsFromJson(req, &hasClusterId); err != nil {
+			httpserver.Error(w, err.Error(), http.StatusBadRequest)
+			return
 		}
-		postBody := bytes.NewBuffer(make([]byte, 0, cl))
-		defer req.Body.Close()
+		clusterId = hasClusterId.ClusterID
+	}
 
-		rdr := io.TeeReader(req.Body, postBody)
+	effectiveMethod := req.Method
+	if req.Method == "POST" && len(params["_method"]) == 1 {
+		effectiveMethod = params["_method"][0]
+	}
 
-		err := json.NewDecoder(rdr).Decode(&hasClusterId)
-		if err != nil {
-			httpserver.Error(w, err.Error(), http.StatusBadRequest)
+	if effectiveMethod == "GET" && clusterId == "" && len(params["filters"]) == 1 {
+		if h.handleMultiClusterQuery(w, req, params, &clusterId) {
 			return
 		}
-		req.Body = ioutil.NopCloser(postBody)
-
-		clusterId = hasClusterId.ClusterID
 	}
+	log.Printf("Clusterid is %q", clusterId)
 
 	if clusterId == "" || clusterId == h.handler.Cluster.ClusterID {
 		h.next.ServeHTTP(w, req)
@@ -331,7 +506,7 @@ func (h *collectionFederatedRequestHandler) ServeHTTP(w http.ResponseWriter, req
 		m = collectionRe.FindStringSubmatch(req.URL.Path)
 		clusterId := ""
 
-		if len(m) == 3 {
+		if len(m) > 0 {
 			clusterId = m[2]
 		}
 
@@ -423,7 +598,7 @@ func (h *Handler) setupProxyRemoteCluster(next http.Handler) http.Handler {
 	mux := http.NewServeMux()
 	mux.Handle("/arvados/v1/workflows", &genericFederatedRequestHandler{next, h, wfRe})
 	mux.Handle("/arvados/v1/workflows/", &genericFederatedRequestHandler{next, h, wfRe})
-	mux.Handle("/arvados/v1/containers", next)
+	mux.Handle("/arvados/v1/containers", &genericFederatedRequestHandler{next, h, containersRe})
 	mux.Handle("/arvados/v1/containers/", &genericFederatedRequestHandler{next, h, containersRe})
 	mux.Handle("/arvados/v1/container_requests", &genericFederatedRequestHandler{next, h, containerRequestsRe})
 	mux.Handle("/arvados/v1/container_requests/", &genericFederatedRequestHandler{next, h, containerRequestsRe})
diff --git a/lib/controller/federation_test.go b/lib/controller/federation_test.go
index 3b11625f6..b94efa6ae 100644
--- a/lib/controller/federation_test.go
+++ b/lib/controller/federation_test.go
@@ -6,7 +6,9 @@ package controller
 
 import (
 	"encoding/json"
+	"fmt"
 	"io/ioutil"
+	"log"
 	"net/http"
 	"net/http/httptest"
 	"net/url"
@@ -613,3 +615,28 @@ func (s *FederationSuite) TestGetRemoteContainer(c *check.C) {
 	c.Check(json.NewDecoder(resp.Body).Decode(&cn), check.IsNil)
 	c.Check(cn.UUID, check.Equals, arvadostest.QueuedContainerUUID)
 }
+
+func (s *FederationSuite) TestListRemoteContainer(c *check.C) {
+	defer s.localServiceReturns404(c).Close()
+	req := httptest.NewRequest("GET", "/arvados/v1/containers?filters="+
+		url.QueryEscape(fmt.Sprintf(`[["uuid", "in", ["%v"]]]`, arvadostest.QueuedContainerUUID)), nil)
+	req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
+	resp := s.testRequest(req)
+	c.Check(resp.StatusCode, check.Equals, http.StatusOK)
+	var cn arvados.ContainerList
+	c.Check(json.NewDecoder(resp.Body).Decode(&cn), check.IsNil)
+	c.Check(cn.Items[0].UUID, check.Equals, arvadostest.QueuedContainerUUID)
+}
+
+func (s *FederationSuite) TestListMultiRemoteContainers(c *check.C) {
+	defer s.localServiceReturns404(c).Close()
+	req := httptest.NewRequest("GET", "/arvados/v1/containers?filters="+
+		url.QueryEscape(fmt.Sprintf(`[["uuid", "in", ["%v", "zhome-xvhdp-cr5queuedcontnr"]]]`, arvadostest.QueuedContainerUUID)), nil)
+	req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
+	resp := s.testRequest(req)
+	log.Printf("got %+v", resp)
+	c.Assert(resp.StatusCode, check.Equals, http.StatusOK)
+	var cn arvados.ContainerList
+	c.Check(json.NewDecoder(resp.Body).Decode(&cn), check.IsNil)
+	c.Check(cn.Items[0].UUID, check.Equals, arvadostest.QueuedContainerUUID)
+}

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


hooks/post-receive
-- 




More information about the arvados-commits mailing list