Add custom Boruta HTTP headers httpapi
authorMaciej Wereski <m.wereski@partner.samsung.com>
Fri, 22 Dec 2017 09:18:29 +0000 (10:18 +0100)
committerMaciej Wereski <m.wereski@partner.samsung.com>
Tue, 5 Jun 2018 10:48:29 +0000 (12:48 +0200)
This patch introduces two custom Boruta headers to replies for some REST
API functions:
* Boruta-Request-Status - contains current status of request. It is
  convenient to request HEAD on GetRequestInfo path to check only status
  of given request.
* Boruta-Request-Count - contains number of requests returned for
  Request List/Filter operation.
* Boruta-Worker-Status - contains current status of worker.
* Boruta-Worker-Count - conttains number of workers returned for Worker
  List/Filter operation.

Change-Id: I871c63058b2690bac50046f0dc500c795843b958
Signed-off-by: Maciej Wereski <m.wereski@partner.samsung.com>
http/server/api/v1/api.go
http/server/api/v1/api_test.go
http/server/api/v1/handlers_test.go

index bc897e1..0fa8dd5 100644 (file)
@@ -71,17 +71,27 @@ func routerSetHandler(grp *httptreemux.Group, path string, fn reqHandler,
                return func(w http.ResponseWriter, r *http.Request,
                        ps map[string]string) {
                        status := status
-                       rdata := handle(r, ps)
-                       if data, isErr := rdata.(*util.ServerError); isErr &&
-                               data != nil {
-                               status = data.Status
+                       data := handle(r, ps)
+                       switch data := data.(type) {
+                       case *util.ServerError:
+                               if data != nil {
+                                       status = data.Status
+                               }
+                       case ReqInfo:
+                               w.Header().Add("Boruta-Request-State", string(data.State))
+                       case []ReqInfo:
+                               w.Header().Add("Boruta-Request-Count", strconv.Itoa(len(data)))
+                       case WorkerInfo:
+                               w.Header().Add("Boruta-Worker-State", string(data.State))
+                       case []WorkerInfo:
+                               w.Header().Add("Boruta-Worker-Count", strconv.Itoa(len(data)))
                        }
                        if status != http.StatusNoContent {
                                w.Header().Set("Content-Type", "application/json")
                        }
                        w.WriteHeader(status)
                        if status != http.StatusNoContent {
-                               w.Write(jsonMustMarshal(rdata))
+                               w.Write(jsonMustMarshal(data))
                        }
                }
        }
index e53dbd6..e8cf5b2 100644 (file)
@@ -65,6 +65,7 @@ type requestTest struct {
        json        string
        contentType string
        status      int
+       header      http.Header
 }
 
 type allMocks struct {
@@ -213,10 +214,17 @@ func runTests(assert *assert.Assertions, r *httptreemux.TreeMux, tests []request
                        if update {
                                continue
                        }
+                       // check Boruta HTTP headers
+                       if test.header != nil && len(test.header) > 0 {
+                               for k, v := range test.header {
+                                       assert.Contains(resp.Header, k, tcaseErrStr+" (header)")
+                                       assert.Equal(v, resp.Header[k], tcaseErrStr+" (header)")
+                               }
+                       }
                        // check result JSON
                        expected, err := ioutil.ReadFile(tdata)
                        assert.Nil(err, tcaseErrStr)
-                       assert.JSONEq(string(expected), string(body), tcaseErrStr)
+                       assert.JSONEq(string(expected), string(body), tcaseErrStr+" (JSON)")
                }
        }
 }
index 29a2d4e..e5746c6 100644 (file)
@@ -194,6 +194,8 @@ func TestGetRequestInfoHandler(t *testing.T) {
        var req ReqInfo
        err := json.Unmarshal([]byte(validReqJSON), &req)
        assert.Nil(err)
+       header := make(http.Header)
+       header.Set("Boruta-Request-State", "WAITING")
 
        notFoundTest := testFromTempl(notFoundTestTempl, prefix, path+"2", methods...)
        invalidIDTest := testFromTempl(invalidIDTestTempl, prefix, path+invalidID, methods...)
@@ -209,6 +211,7 @@ func TestGetRequestInfoHandler(t *testing.T) {
                        json:        ``,
                        contentType: contentTypeJSON,
                        status:      http.StatusOK,
+                       header:      header,
                },
                // Try to get request information of request that doesn't exist.
                notFoundTest,
@@ -245,13 +248,19 @@ func TestListRequestsHandler(t *testing.T) {
 
        validFilter := util.NewRequestFilter("WAIT", "")
        m.rq.EXPECT().ListRequests(validFilter).Return(reqs[:2], nil)
+       validHeader := make(http.Header)
+       validHeader.Set("Boruta-Request-Count", "2")
 
        emptyFilter := util.NewRequestFilter("", "")
        m.rq.EXPECT().ListRequests(emptyFilter).Return(reqs, nil).Times(2)
        m.rq.EXPECT().ListRequests(nil).Return(reqs, nil).Times(3)
+       allHeader := make(http.Header)
+       allHeader.Set("Boruta-Request-Count", "4")
 
        missingFilter := util.NewRequestFilter("INVALID", "")
        m.rq.EXPECT().ListRequests(missingFilter).Return([]ReqInfo{}, nil)
+       missingHeader := make(http.Header)
+       missingHeader.Set("Boruta-Request-Count", "0")
 
        // Currently ListRequests doesn't return any error hence the meaningless values.
        badFilter := util.NewRequestFilter("FAIL", "-1")
@@ -266,6 +275,7 @@ func TestListRequestsHandler(t *testing.T) {
                        json:        string(jsonMustMarshal(validFilter)),
                        contentType: contentTypeJSON,
                        status:      http.StatusOK,
+                       header:      validHeader,
                },
                // List all requests.
                {
@@ -275,6 +285,7 @@ func TestListRequestsHandler(t *testing.T) {
                        json:        ``,
                        contentType: contentTypeJSON,
                        status:      http.StatusOK,
+                       header:      allHeader,
                },
                // Empty body - list all requests.
                {
@@ -284,6 +295,7 @@ func TestListRequestsHandler(t *testing.T) {
                        json:        "",
                        contentType: contentTypeJSON,
                        status:      http.StatusOK,
+                       header:      allHeader,
                },
                // Nil filter - list all requests (same as emptyFilter).
                {
@@ -293,6 +305,7 @@ func TestListRequestsHandler(t *testing.T) {
                        json:        string(jsonMustMarshal(nil)),
                        contentType: contentTypeJSON,
                        status:      http.StatusOK,
+                       header:      allHeader,
                },
                // Empty filter - list all requests.
                {
@@ -302,6 +315,7 @@ func TestListRequestsHandler(t *testing.T) {
                        json:        string(jsonMustMarshal(emptyFilter)),
                        contentType: contentTypeJSON,
                        status:      http.StatusOK,
+                       header:      allHeader,
                },
                // No matches
                {
@@ -311,6 +325,7 @@ func TestListRequestsHandler(t *testing.T) {
                        json:        string(jsonMustMarshal(missingFilter)),
                        contentType: contentTypeJSON,
                        status:      http.StatusOK,
+                       header:      missingHeader,
                },
                // Error
                {
@@ -425,14 +440,20 @@ func TestListWorkersHandler(t *testing.T) {
                Capabilities: map[string]string{"architecture": "AArch64"},
        }
        m.wm.EXPECT().ListWorkers(validFilter.Groups, validFilter.Capabilities).Return(workers[:2], nil)
+       validHeader := make(http.Header)
+       validHeader.Set("Boruta-Worker-Count", "2")
 
        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)
+       allHeader := make(http.Header)
+       allHeader.Set("Boruta-Worker-Count", "4")
 
        missingFilter := util.WorkersFilter{
                Groups: Groups{"Fern Flower"},
        }
+       missingHeader := make(http.Header)
+       missingHeader.Set("Boruta-Worker-Count", "0")
        m.wm.EXPECT().ListWorkers(missingFilter.Groups, missingFilter.Capabilities).Return([]WorkerInfo{}, nil)
 
        // Currently ListWorkers doesn't return any error hence the meaningless values.
@@ -450,6 +471,7 @@ func TestListWorkersHandler(t *testing.T) {
                        json:        string(jsonMustMarshal(validFilter)),
                        contentType: contentTypeJSON,
                        status:      http.StatusOK,
+                       header:      validHeader,
                },
                // List all requests.
                {
@@ -459,6 +481,7 @@ func TestListWorkersHandler(t *testing.T) {
                        json:        ``,
                        contentType: contentTypeJSON,
                        status:      http.StatusOK,
+                       header:      allHeader,
                },
                // Empty body - list all requests.
                {
@@ -468,6 +491,7 @@ func TestListWorkersHandler(t *testing.T) {
                        json:        ``,
                        contentType: contentTypeJSON,
                        status:      http.StatusOK,
+                       header:      allHeader,
                },
                // Empty filter (all nil) - list all requests.
                {
@@ -477,6 +501,7 @@ func TestListWorkersHandler(t *testing.T) {
                        json:        string(jsonMustMarshal(util.WorkersFilter{nil, nil})),
                        contentType: contentTypeJSON,
                        status:      http.StatusOK,
+                       header:      allHeader,
                },
                // Empty filter (nil groups) - list all requests.
                {
@@ -486,6 +511,7 @@ func TestListWorkersHandler(t *testing.T) {
                        json:        string(jsonMustMarshal(util.WorkersFilter{nil, make(Capabilities)})),
                        contentType: contentTypeJSON,
                        status:      http.StatusOK,
+                       header:      allHeader,
                },
                // Empty filter (nil caps) - list all requests.
                {
@@ -495,6 +521,7 @@ func TestListWorkersHandler(t *testing.T) {
                        json:        string(jsonMustMarshal(util.WorkersFilter{Groups{}, nil})),
                        contentType: contentTypeJSON,
                        status:      http.StatusOK,
+                       header:      allHeader,
                },
                // No matches.
                {
@@ -504,6 +531,7 @@ func TestListWorkersHandler(t *testing.T) {
                        json:        string(jsonMustMarshal(missingFilter)),
                        contentType: contentTypeJSON,
                        status:      http.StatusOK,
+                       header:      missingHeader,
                },
                // Error.
                {
@@ -533,6 +561,8 @@ func TestGetWorkerInfoHandler(t *testing.T) {
        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)
+       header := make(http.Header)
+       header.Set("Boruta-Worker-State", "IDLE")
 
        tests := []requestTest{
                {
@@ -542,6 +572,7 @@ func TestGetWorkerInfoHandler(t *testing.T) {
                        json:        ``,
                        contentType: contentTypeJSON,
                        status:      http.StatusOK,
+                       header:      header,
                },
                {
                        name:        prefix + "bad-uuid",