From c2eb4e9e2696b098b6f67c911999620a1ef79a90 Mon Sep 17 00:00:00 2001 From: Maciej Wereski Date: Tue, 17 Oct 2017 12:53:38 +0200 Subject: [PATCH] HTTP API: Implement filtering workers Change-Id: I93137f8e3c7dc66c859de671c316a443ec4aad9e Signed-off-by: Maciej Wereski --- server/api/v1/api_test.go | 12 ++- server/api/v1/filter.go | 6 ++ server/api/v1/handlers.go | 17 +++- server/api/v1/handlers_test.go | 107 +++++++++++++++++++-- .../testdata/filter-workers-bad-filter-POST.json | 1 + .../testdata/filter-workers-empty-body-POST.json | 1 + .../testdata/filter-workers-empty-filter-POST.json | 1 + .../filter-workers-empty2-filter-POST.json | 1 + .../filter-workers-empty3-filter-POST.json | 1 + .../filter-workers-malformed-json-POST.json | 1 + .../v1/testdata/filter-workers-nomatch-POST.json | 1 + .../testdata/filter-workers-valid-filter-POST.json | 1 + server/api/v1/testdata/list-workers-all-GET.json | 2 +- .../api/v1/testdata/list-workers-filter-POST.json | 1 - 14 files changed, 141 insertions(+), 12 deletions(-) create mode 100644 server/api/v1/testdata/filter-workers-bad-filter-POST.json create mode 100644 server/api/v1/testdata/filter-workers-empty-body-POST.json create mode 100644 server/api/v1/testdata/filter-workers-empty-filter-POST.json create mode 100644 server/api/v1/testdata/filter-workers-empty2-filter-POST.json create mode 100644 server/api/v1/testdata/filter-workers-empty3-filter-POST.json create mode 100644 server/api/v1/testdata/filter-workers-malformed-json-POST.json create mode 100644 server/api/v1/testdata/filter-workers-nomatch-POST.json create mode 100644 server/api/v1/testdata/filter-workers-valid-filter-POST.json delete mode 100644 server/api/v1/testdata/list-workers-filter-POST.json diff --git a/server/api/v1/api_test.go b/server/api/v1/api_test.go index 0883f7d..23f53f2 100644 --- a/server/api/v1/api_test.go +++ b/server/api/v1/api_test.go @@ -146,14 +146,20 @@ func testFromTempl(templ *requestTest, name string, path string, return } -func newWorker(uuid string, state WorkerState) WorkerInfo { - caps := make(Capabilities) +func newWorker(uuid string, state WorkerState, groups Groups, caps Capabilities) (w WorkerInfo) { + if caps == nil { + caps = make(Capabilities) + } caps["UUID"] = uuid - return WorkerInfo{ + w = WorkerInfo{ WorkerUUID: WorkerUUID(uuid), State: state, Caps: caps, } + if len(groups) != 0 { + w.Groups = groups + } + return } func runTests(assert *assert.Assertions, api *API, tests []requestTest) { diff --git a/server/api/v1/filter.go b/server/api/v1/filter.go index 3fb830a..e98c499 100644 --- a/server/api/v1/filter.go +++ b/server/api/v1/filter.go @@ -25,6 +25,12 @@ import ( . "git.tizen.org/tools/boruta" ) +// WorkersFilter contains Groups and Capabilities to be used to filter workers. +type WorkersFilter struct { + Groups + Capabilities +} + // RequestFilter implements ListFilter interface. Currently it is possible to // filter by state and priority. type RequestFilter struct { diff --git a/server/api/v1/handlers.go b/server/api/v1/handlers.go index 850c009..2b92504 100644 --- a/server/api/v1/handlers.go +++ b/server/api/v1/handlers.go @@ -169,7 +169,22 @@ func (api *API) prolongAccessHandler(r *http.Request, ps map[string]string) resp // listWorkersHandler parses HTTP request for listing workers and calls ListWorkers(). func (api *API) listWorkersHandler(r *http.Request, ps map[string]string) responseData { - return newServerError(ErrNotImplemented, "list workers") + var filter WorkersFilter + defer r.Body.Close() + + if r.Method == http.MethodPost { + err := json.NewDecoder(r.Body).Decode(&filter) + if err != nil && err != io.EOF { + return newServerError(err) + } + } + + workers, err := api.workers.ListWorkers(filter.Groups, filter.Capabilities) + if err != nil { + return newServerError(err) + } + + return workers } // getWorkerInfoHandler parses HTTP request for obtaining worker information and diff --git a/server/api/v1/handlers_test.go b/server/api/v1/handlers_test.go index 6fba4eb..d5cd366 100644 --- a/server/api/v1/handlers_test.go +++ b/server/api/v1/handlers_test.go @@ -402,23 +402,118 @@ func TestListWorkersHandler(t *testing.T) { assert, m, api := initTest(t) defer m.finish() + armCaps := make(Capabilities) + armCaps["architecture"] = "AArch64" + riscvCaps := make(Capabilities) + riscvCaps["architecture"] = "RISC-V" + + workers := []WorkerInfo{ + newWorker("0", IDLE, Groups{"Lędzianie"}, armCaps), + newWorker("1", FAIL, Groups{"Malinowy Chruśniak"}, armCaps), + newWorker("2", IDLE, Groups{"Malinowy Chruśniak", "Lędzianie"}, riscvCaps), + newWorker("3", FAIL, Groups{"Malinowy Chruśniak"}, riscvCaps), + } + + methods := []string{http.MethodPost} + prefix := "filter-workers-" + filterPath := "/api/v1/workers/list" + malformedJSONTest := testFromTempl(malformedJSONTestTempl, prefix, filterPath, methods...) + + validFilter := WorkersFilter{ + Groups: Groups{"Lędzianie"}, + Capabilities: map[string]string{"architecture": "AArch64"}, + } + m.wm.EXPECT().ListWorkers(validFilter.Groups, validFilter.Capabilities).Return(workers[:2], nil) + + m.wm.EXPECT().ListWorkers(nil, nil).Return(workers, nil).MinTimes(1) + m.wm.EXPECT().ListWorkers(Groups{}, nil).Return(workers, nil) + m.wm.EXPECT().ListWorkers(nil, make(Capabilities)).Return(workers, nil) + + missingFilter := WorkersFilter{ + Groups: Groups{"Fern Flower"}, + } + m.wm.EXPECT().ListWorkers(missingFilter.Groups, missingFilter.Capabilities).Return([]WorkerInfo{}, nil) + + // Currently ListWorkers doesn't return any error hence the meaningless values. + badFilter := WorkersFilter{ + Groups: Groups{"Oops"}, + } + m.wm.EXPECT().ListWorkers(badFilter.Groups, badFilter.Capabilities).Return([]WorkerInfo{}, errors.New("foo bar: pizza failed")) + tests := []requestTest{ + // Valid filter - list some requests. + { + name: prefix + "valid-filter", + path: filterPath, + methods: methods, + json: string(jsonMustMarshal(validFilter)), + contentType: contentTypeJSON, + status: http.StatusOK, + }, + // List all requests. { name: "list-workers-all", path: "/api/v1/workers/", methods: []string{http.MethodGet, http.MethodHead}, json: ``, contentType: contentTypeJSON, - status: http.StatusNotImplemented, + status: http.StatusOK, }, + // Empty body - list all requests. { - name: "list-workers-filter", - path: "/api/v1/workers/list", - methods: []string{http.MethodPost}, + name: prefix + "empty-body", + path: filterPath, + methods: methods, json: ``, contentType: contentTypeJSON, - status: http.StatusNotImplemented, + status: http.StatusOK, }, + // Empty filter (all nil) - list all requests. + { + name: prefix + "empty-filter", + path: filterPath, + methods: methods, + json: string(jsonMustMarshal(WorkersFilter{nil, nil})), + contentType: contentTypeJSON, + status: http.StatusOK, + }, + // Empty filter (nil groups) - list all requests. + { + name: prefix + "empty2-filter", + path: filterPath, + methods: methods, + json: string(jsonMustMarshal(WorkersFilter{nil, make(Capabilities)})), + contentType: contentTypeJSON, + status: http.StatusOK, + }, + // Empty filter (nil caps) - list all requests. + { + name: prefix + "empty3-filter", + path: filterPath, + methods: methods, + json: string(jsonMustMarshal(WorkersFilter{Groups{}, nil})), + contentType: contentTypeJSON, + status: http.StatusOK, + }, + // No matches. + { + name: prefix + "nomatch", + path: filterPath, + methods: methods, + json: string(jsonMustMarshal(missingFilter)), + contentType: contentTypeJSON, + status: http.StatusOK, + }, + // Error. + { + name: prefix + "bad-filter", + path: filterPath, + methods: methods, + json: string(jsonMustMarshal(badFilter)), + contentType: contentTypeJSON, + status: http.StatusBadRequest, + }, + malformedJSONTest, } runTests(assert, api, tests) @@ -433,7 +528,7 @@ func TestGetWorkerInfoHandler(t *testing.T) { methods := []string{http.MethodGet, http.MethodHead} notFoundTest := testFromTempl(notFoundTestTempl, prefix, path+missingUUID, methods...) - worker := newWorker(validUUID, IDLE) + worker := newWorker(validUUID, IDLE, Groups{}, nil) missingErr := NotFoundError("Worker") m.wm.EXPECT().GetWorkerInfo(WorkerUUID(validUUID)).Return(worker, nil).Times(2) m.wm.EXPECT().GetWorkerInfo(WorkerUUID(missingUUID)).Return(WorkerInfo{}, missingErr).Times(2) diff --git a/server/api/v1/testdata/filter-workers-bad-filter-POST.json b/server/api/v1/testdata/filter-workers-bad-filter-POST.json new file mode 100644 index 0000000..f6dd9fd --- /dev/null +++ b/server/api/v1/testdata/filter-workers-bad-filter-POST.json @@ -0,0 +1 @@ +{"error":"invalid request: foo bar: pizza failed"} \ No newline at end of file diff --git a/server/api/v1/testdata/filter-workers-empty-body-POST.json b/server/api/v1/testdata/filter-workers-empty-body-POST.json new file mode 100644 index 0000000..a7d5746 --- /dev/null +++ b/server/api/v1/testdata/filter-workers-empty-body-POST.json @@ -0,0 +1 @@ +[{"WorkerUUID":"0","State":"IDLE","Groups":["Lędzianie"],"Caps":{"UUID":"1","architecture":"AArch64"}},{"WorkerUUID":"1","State":"FAILED","Groups":["Malinowy Chruśniak"],"Caps":{"UUID":"1","architecture":"AArch64"}},{"WorkerUUID":"2","State":"IDLE","Groups":["Malinowy Chruśniak","Lędzianie"],"Caps":{"UUID":"3","architecture":"RISC-V"}},{"WorkerUUID":"3","State":"FAILED","Groups":["Malinowy Chruśniak"],"Caps":{"UUID":"3","architecture":"RISC-V"}}] \ No newline at end of file diff --git a/server/api/v1/testdata/filter-workers-empty-filter-POST.json b/server/api/v1/testdata/filter-workers-empty-filter-POST.json new file mode 100644 index 0000000..a7d5746 --- /dev/null +++ b/server/api/v1/testdata/filter-workers-empty-filter-POST.json @@ -0,0 +1 @@ +[{"WorkerUUID":"0","State":"IDLE","Groups":["Lędzianie"],"Caps":{"UUID":"1","architecture":"AArch64"}},{"WorkerUUID":"1","State":"FAILED","Groups":["Malinowy Chruśniak"],"Caps":{"UUID":"1","architecture":"AArch64"}},{"WorkerUUID":"2","State":"IDLE","Groups":["Malinowy Chruśniak","Lędzianie"],"Caps":{"UUID":"3","architecture":"RISC-V"}},{"WorkerUUID":"3","State":"FAILED","Groups":["Malinowy Chruśniak"],"Caps":{"UUID":"3","architecture":"RISC-V"}}] \ No newline at end of file diff --git a/server/api/v1/testdata/filter-workers-empty2-filter-POST.json b/server/api/v1/testdata/filter-workers-empty2-filter-POST.json new file mode 100644 index 0000000..a7d5746 --- /dev/null +++ b/server/api/v1/testdata/filter-workers-empty2-filter-POST.json @@ -0,0 +1 @@ +[{"WorkerUUID":"0","State":"IDLE","Groups":["Lędzianie"],"Caps":{"UUID":"1","architecture":"AArch64"}},{"WorkerUUID":"1","State":"FAILED","Groups":["Malinowy Chruśniak"],"Caps":{"UUID":"1","architecture":"AArch64"}},{"WorkerUUID":"2","State":"IDLE","Groups":["Malinowy Chruśniak","Lędzianie"],"Caps":{"UUID":"3","architecture":"RISC-V"}},{"WorkerUUID":"3","State":"FAILED","Groups":["Malinowy Chruśniak"],"Caps":{"UUID":"3","architecture":"RISC-V"}}] \ No newline at end of file diff --git a/server/api/v1/testdata/filter-workers-empty3-filter-POST.json b/server/api/v1/testdata/filter-workers-empty3-filter-POST.json new file mode 100644 index 0000000..a7d5746 --- /dev/null +++ b/server/api/v1/testdata/filter-workers-empty3-filter-POST.json @@ -0,0 +1 @@ +[{"WorkerUUID":"0","State":"IDLE","Groups":["Lędzianie"],"Caps":{"UUID":"1","architecture":"AArch64"}},{"WorkerUUID":"1","State":"FAILED","Groups":["Malinowy Chruśniak"],"Caps":{"UUID":"1","architecture":"AArch64"}},{"WorkerUUID":"2","State":"IDLE","Groups":["Malinowy Chruśniak","Lędzianie"],"Caps":{"UUID":"3","architecture":"RISC-V"}},{"WorkerUUID":"3","State":"FAILED","Groups":["Malinowy Chruśniak"],"Caps":{"UUID":"3","architecture":"RISC-V"}}] \ No newline at end of file diff --git a/server/api/v1/testdata/filter-workers-malformed-json-POST.json b/server/api/v1/testdata/filter-workers-malformed-json-POST.json new file mode 100644 index 0000000..c59dde1 --- /dev/null +++ b/server/api/v1/testdata/filter-workers-malformed-json-POST.json @@ -0,0 +1 @@ +{"error":"invalid request: unexpected EOF"} \ No newline at end of file diff --git a/server/api/v1/testdata/filter-workers-nomatch-POST.json b/server/api/v1/testdata/filter-workers-nomatch-POST.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/server/api/v1/testdata/filter-workers-nomatch-POST.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/server/api/v1/testdata/filter-workers-valid-filter-POST.json b/server/api/v1/testdata/filter-workers-valid-filter-POST.json new file mode 100644 index 0000000..020b18c --- /dev/null +++ b/server/api/v1/testdata/filter-workers-valid-filter-POST.json @@ -0,0 +1 @@ +[{"WorkerUUID":"0","State":"IDLE","Groups":["Lędzianie"],"Caps":{"UUID":"1","architecture":"AArch64"}},{"WorkerUUID":"1","State":"FAILED","Groups":["Malinowy Chruśniak"],"Caps":{"UUID":"1","architecture":"AArch64"}}] \ No newline at end of file diff --git a/server/api/v1/testdata/list-workers-all-GET.json b/server/api/v1/testdata/list-workers-all-GET.json index 7fb8fd4..a7d5746 100644 --- a/server/api/v1/testdata/list-workers-all-GET.json +++ b/server/api/v1/testdata/list-workers-all-GET.json @@ -1 +1 @@ -{"error":"not implemented yet: list workers"} \ No newline at end of file +[{"WorkerUUID":"0","State":"IDLE","Groups":["Lędzianie"],"Caps":{"UUID":"1","architecture":"AArch64"}},{"WorkerUUID":"1","State":"FAILED","Groups":["Malinowy Chruśniak"],"Caps":{"UUID":"1","architecture":"AArch64"}},{"WorkerUUID":"2","State":"IDLE","Groups":["Malinowy Chruśniak","Lędzianie"],"Caps":{"UUID":"3","architecture":"RISC-V"}},{"WorkerUUID":"3","State":"FAILED","Groups":["Malinowy Chruśniak"],"Caps":{"UUID":"3","architecture":"RISC-V"}}] \ No newline at end of file diff --git a/server/api/v1/testdata/list-workers-filter-POST.json b/server/api/v1/testdata/list-workers-filter-POST.json deleted file mode 100644 index 7fb8fd4..0000000 --- a/server/api/v1/testdata/list-workers-filter-POST.json +++ /dev/null @@ -1 +0,0 @@ -{"error":"not implemented yet: list workers"} \ No newline at end of file -- 2.7.4