* limitations under the License
*/
-// File server/api/v1/errors.go provides errors that may occur when interacting
-// with Boruta HTTP API.
+// File http/errors.go provides errors that may occur when interacting with
+// Boruta HTTP API.
-package v1
+package http
import (
"errors"
. "git.tizen.org/tools/boruta"
)
-// serverError represents error that occured while creating response.
-type serverError struct {
+// ServerError represents error that occured while creating response.
+type ServerError struct {
// Err contains general error string.
Err string `json:"error"`
// Status contains HTTP error code that should be returned with the error.
return ok
}
-// newServerError provides pointer to initialized serverError.
-func newServerError(err error, details ...string) (ret *serverError) {
+// NewServerError provides pointer to initialized ServerError.
+func NewServerError(err error, details ...string) (ret *ServerError) {
if err == nil {
return nil
}
- ret = new(serverError)
+ ret = new(ServerError)
ret.Err = err.Error()
if len(details) > 0 {
--- /dev/null
+/*
+ * Copyright (c) 2017-2018 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+// File http/error_test.go provides test of Boruta HTTP server errors.
+
+package http
+
+import (
+ "errors"
+ "io"
+ "net/http"
+ "testing"
+
+ . "git.tizen.org/tools/boruta"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestNewServerError(t *testing.T) {
+ assert := assert.New(t)
+ badRequest := &ServerError{
+ Err: "invalid request: foo",
+ Status: http.StatusBadRequest,
+ }
+ nobody := "no body provided in HTTP request"
+ missingBody := &ServerError{
+ Err: nobody,
+ Status: http.StatusBadRequest,
+ }
+ notImplemented := &ServerError{
+ Err: ErrNotImplemented.Error(),
+ Status: http.StatusNotImplemented,
+ }
+ internalErr := &ServerError{
+ Err: ErrInternalServerError.Error(),
+ Status: http.StatusInternalServerError,
+ }
+ customErr := &ServerError{
+ Err: "invalid request: more details",
+ Status: http.StatusBadRequest,
+ }
+ notFound := &ServerError{
+ Err: NotFoundError("Fern Flower").Error(),
+ Status: http.StatusNotFound,
+ }
+ assert.Equal(badRequest, NewServerError(errors.New("foo")))
+ assert.Equal(missingBody, NewServerError(io.EOF))
+ assert.Equal(notImplemented, NewServerError(ErrNotImplemented))
+ assert.Equal(internalErr, NewServerError(ErrInternalServerError))
+ assert.Equal(customErr, NewServerError(ErrBadRequest, "more details"))
+ assert.Equal(notFound, NewServerError(NotFoundError("Fern Flower")))
+ assert.Nil(NewServerError(nil))
+}
* limitations under the License
*/
-// File server/api/v1/filter.go provides implementation of ListFilter interface.
+// File http/filter.go provides implementation of ListFilter interface.
-package v1
+package http
import (
"strconv"
* limitations under the License
*/
-// File server/api/v1/filter_test.go contains additional tests for RequestFilter.
+// File http/filter_test.go contains tests for RequestFilter.
-package v1
+package http
import (
"strconv"
--- /dev/null
+/*
+ * Copyright (c) 2017-2018 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+// Package http provides datatypes that are shared between server and client.
+package http
+
+import (
+ "net"
+
+ . "git.tizen.org/tools/boruta"
+)
+
+// ReqIDPack is used for JSON (un)marshaller.
+type ReqIDPack struct {
+ ReqID
+}
+
+// WorkerStatePack is used by JSON (un)marshaller.
+type WorkerStatePack struct {
+ WorkerState
+}
+
+// AccessInfo2 structure is used by HTTP instead of AccessInfo when acquiring
+// worker. The only difference is that key field is in PEM format instead of
+// rsa.PrivateKey. It is temporary solution - session private keys will be
+// replaces with users' public keys when proper user support is added.
+type AccessInfo2 struct {
+ // Addr is necessary information to connect to a tunnel to Dryad.
+ Addr net.Addr
+ // Key is private RSA key in PEM format.
+ Key string
+ // Username is a login name for the job session.
+ Username string
+}
import (
"encoding/json"
"fmt"
- "net"
"net/http"
"regexp"
"strconv"
. "git.tizen.org/tools/boruta"
+ util "git.tizen.org/tools/boruta/http"
"github.com/dimfeld/httptreemux"
)
// Returned values are directly converted to JSON responses.
type responseData interface{}
-// reqIDPack is used as input for JSON (un)marshaller.
-type reqIDPack struct {
- ReqID
-}
-
-// AccessInfo2 structure is used by HTTP instead of AccessInfo when acquiring
-// worker. The only difference is that key field is in PEM format instead of
-// rsa.PrivateKey. It is temporary solution - session private keys will be
-// replaces with users' public keys when proper user support is added.
-type AccessInfo2 struct {
- // Addr is necessary information to connect to a tunnel to Dryad.
- Addr net.Addr
- // Key is private RSA key in PEM format.
- Key string
- // Username is a login name for the job session.
- Username string
-}
-
-// workerStatePack is used as input for JSON (un)marshaller.
-type workerStatePack struct {
- WorkerState
-}
-
// reqHandler denotes function that parses HTTP request and returns responseData.
type reqHandler func(*http.Request, map[string]string) responseData
res, err := json.Marshal(data)
if err != nil {
msg := "unable to marshal JSON:" + err.Error()
- panic(newServerError(ErrInternalServerError, msg))
+ panic(util.NewServerError(util.ErrInternalServerError, msg))
}
return res
}
var reason interface{}
var status = http.StatusInternalServerError
switch srvErr := err.(type) {
- case *serverError:
+ case *util.ServerError:
reason = srvErr.Err
status = srvErr.Status
default:
ps map[string]string) {
status := status
rdata := handle(r, ps)
- if data, isErr := rdata.(*serverError); isErr &&
+ if data, isErr := rdata.(*util.ServerError); isErr &&
data != nil {
status = data.Status
}
package v1
import (
- "errors"
"flag"
- "io"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
. "git.tizen.org/tools/boruta"
+ util "git.tizen.org/tools/boruta/http"
"git.tizen.org/tools/boruta/mocks"
"github.com/dimfeld/httptreemux"
"github.com/golang/mock/gomock"
m.finish()
}
-func TestNewServerError(t *testing.T) {
- assert := assert.New(t)
- badRequest := &serverError{
- Err: "invalid request: foo",
- Status: http.StatusBadRequest,
- }
- nobody := "no body provided in HTTP request"
- missingBody := &serverError{
- Err: nobody,
- Status: http.StatusBadRequest,
- }
- notImplemented := &serverError{
- Err: ErrNotImplemented.Error(),
- Status: http.StatusNotImplemented,
- }
- internalErr := &serverError{
- Err: ErrInternalServerError.Error(),
- Status: http.StatusInternalServerError,
- }
- customErr := &serverError{
- Err: "invalid request: more details",
- Status: http.StatusBadRequest,
- }
- notFound := &serverError{
- Err: NotFoundError("Fern Flower").Error(),
- Status: http.StatusNotFound,
- }
- assert.Equal(badRequest, newServerError(errors.New("foo")))
- assert.Equal(missingBody, newServerError(io.EOF))
- assert.Equal(notImplemented, newServerError(ErrNotImplemented))
- assert.Equal(internalErr, newServerError(ErrInternalServerError))
- assert.Equal(customErr, newServerError(ErrBadRequest, "more details"))
- assert.Equal(notFound, newServerError(NotFoundError("Fern Flower")))
- assert.Nil(newServerError(nil))
-}
-
func TestJsonMustMarshal(t *testing.T) {
assert := assert.New(t)
assert.Panics(func() { jsonMustMarshal(make(chan bool)) })
{
name: "panic-server-error",
path: "/priv/api/panic/srvErr/",
- err: &serverError{
+ err: &util.ServerError{
Err: msg,
Status: http.StatusInternalServerError,
},
* limitations under the License
*/
-// File server/api/v1/handlers.go contains all handlers that are used in v1 API.
+// File http/server/api/v1/handlers.go contain all handlers that are used in v1 API.
package v1
"net/http"
. "git.tizen.org/tools/boruta"
+ util "git.tizen.org/tools/boruta/http"
)
// newRequestHandler parses HTTP request for creating new Boruta request and
defer r.Body.Close()
if err := json.NewDecoder(r.Body).Decode(&newReq); err != nil {
- return newServerError(err)
+ return util.NewServerError(err)
}
//FIXME: currently UserInfo is ignored. Change when user support is added.
rid, err := api.reqs.NewRequest(newReq.Caps, newReq.Priority, UserInfo{},
newReq.ValidAfter.UTC(), newReq.Deadline.UTC())
if err != nil {
- return newServerError(err)
+ return util.NewServerError(err)
}
- return reqIDPack{rid}
+ return util.ReqIDPack{rid}
}
// closeRequestHandler parses HTTP request for closing existing Boruta request
reqid, err := parseReqID(ps["id"])
if err != nil {
- return newServerError(ErrBadID)
+ return util.NewServerError(util.ErrBadID)
}
- return newServerError(api.reqs.CloseRequest(reqid))
+ return util.NewServerError(api.reqs.CloseRequest(reqid))
}
// updateRequestHandler parses HTTP request for modification of existing Boruta
reqid, err := parseReqID(ps["id"])
if err != nil {
- return newServerError(ErrBadID)
+ return util.NewServerError(util.ErrBadID)
}
var req ReqInfo
if err = json.NewDecoder(r.Body).Decode(&req); err != nil {
- return newServerError(err)
+ return util.NewServerError(err)
}
if req.ID != 0 && req.ID != reqid {
- return newServerError(ErrIDMismatch)
+ return util.NewServerError(util.ErrIDMismatch)
}
// When ID wasn't set in JSON (or was set to 0) then it should be set to
// the value from URL.
req.ID = reqid
- return newServerError(api.reqs.UpdateRequest(&req))
+ return util.NewServerError(api.reqs.UpdateRequest(&req))
}
// getRequestInfoHandler parses HTTP request for getting information about Boruta
reqid, err := parseReqID(ps["id"])
if err != nil {
- return newServerError(ErrBadID)
+ return util.NewServerError(util.ErrBadID)
}
reqinfo, err := api.reqs.GetRequestInfo(reqid)
if err != nil {
- return newServerError(err)
+ return util.NewServerError(err)
}
return reqinfo
// listRequestsHandler parses HTTP request for listing Boruta requests and calls
// ListRequests().
func (api *API) listRequestsHandler(r *http.Request, ps map[string]string) responseData {
- var filter *RequestFilter
+ var filter *util.RequestFilter
defer r.Body.Close()
if r.Method == http.MethodPost {
- filter = new(RequestFilter)
+ filter = new(util.RequestFilter)
if err := json.NewDecoder(r.Body).Decode(filter); err != nil {
if err != io.EOF {
- return newServerError(err)
+ return util.NewServerError(err)
}
filter = nil
}
reqs, err := api.reqs.ListRequests(filter)
if err != nil {
- return newServerError(err)
+ return util.NewServerError(err)
}
return reqs
reqid, err := parseReqID(ps["id"])
if err != nil {
- return newServerError(ErrBadID)
+ return util.NewServerError(util.ErrBadID)
}
accessInfo, err := api.reqs.AcquireWorker(reqid)
if err != nil {
- return newServerError(err)
+ return util.NewServerError(err)
}
key := string(pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(&accessInfo.Key),
}))
- return AccessInfo2{
+ return util.AccessInfo2{
Addr: accessInfo.Addr,
Key: key,
Username: accessInfo.Username,
reqid, err := parseReqID(ps["id"])
if err != nil {
- return newServerError(ErrBadID)
+ return util.NewServerError(util.ErrBadID)
}
- return newServerError(api.reqs.ProlongAccess(reqid))
+ return util.NewServerError(api.reqs.ProlongAccess(reqid))
}
// listWorkersHandler parses HTTP request for listing workers and calls ListWorkers().
func (api *API) listWorkersHandler(r *http.Request, ps map[string]string) responseData {
- var filter WorkersFilter
+ var filter util.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)
+ return util.NewServerError(err)
}
}
workers, err := api.workers.ListWorkers(filter.Groups, filter.Capabilities)
if err != nil {
- return newServerError(err)
+ return util.NewServerError(err)
}
return workers
defer r.Body.Close()
if !isValidUUID(ps["id"]) {
- return newServerError(ErrBadUUID)
+ return util.NewServerError(util.ErrBadUUID)
}
workerinfo, err := api.workers.GetWorkerInfo(WorkerUUID(ps["id"]))
if err != nil {
- return newServerError(err)
+ return util.NewServerError(err)
}
return workerinfo
// setWorkerStateHandler parses HTTP workers for setting worker state and calls
// workers.SetState().
func (api *API) setWorkerStateHandler(r *http.Request, ps map[string]string) responseData {
- var state workerStatePack
+ var state util.WorkerStatePack
defer r.Body.Close()
if !isValidUUID(ps["id"]) {
- return newServerError(ErrBadUUID)
+ return util.NewServerError(util.ErrBadUUID)
}
if err := json.NewDecoder(r.Body).Decode(&state); err != nil {
- return newServerError(err)
+ return util.NewServerError(err)
}
- return newServerError(api.workers.SetState(WorkerUUID(ps["id"]),
+ return util.NewServerError(api.workers.SetState(WorkerUUID(ps["id"]),
state.WorkerState))
}
defer r.Body.Close()
if !isValidUUID(ps["id"]) {
- return newServerError(ErrBadUUID)
+ return util.NewServerError(util.ErrBadUUID)
}
if err := json.NewDecoder(r.Body).Decode(&groups); err != nil {
- return newServerError(err)
+ return util.NewServerError(err)
}
- return newServerError(api.workers.SetGroups(WorkerUUID(ps["id"]), groups))
+ return util.NewServerError(api.workers.SetGroups(WorkerUUID(ps["id"]), groups))
}
// workerDeregister parses HTTP workers for deregistering worker state and calls
defer r.Body.Close()
if !isValidUUID(ps["id"]) {
- return newServerError(ErrBadUUID)
+ return util.NewServerError(util.ErrBadUUID)
}
- return newServerError(api.workers.Deregister(WorkerUUID(ps["id"])))
+ return util.NewServerError(api.workers.Deregister(WorkerUUID(ps["id"])))
}
"time"
. "git.tizen.org/tools/boruta"
+ util "git.tizen.org/tools/boruta/http"
"git.tizen.org/tools/boruta/requests"
)
filterPath := "/api/v1/reqs/list"
malformedJSONTest := testFromTempl(malformedJSONTestTempl, prefix, filterPath, methods...)
- validFilter := NewRequestFilter("WAIT", "")
+ validFilter := util.NewRequestFilter("WAIT", "")
m.rq.EXPECT().ListRequests(validFilter).Return(reqs[:2], nil)
- emptyFilter := NewRequestFilter("", "")
+ emptyFilter := util.NewRequestFilter("", "")
m.rq.EXPECT().ListRequests(emptyFilter).Return(reqs, nil).Times(2)
m.rq.EXPECT().ListRequests(nil).Return(reqs, nil).Times(3)
- missingFilter := NewRequestFilter("INVALID", "")
+ missingFilter := util.NewRequestFilter("INVALID", "")
m.rq.EXPECT().ListRequests(missingFilter).Return([]ReqInfo{}, nil)
// Currently ListRequests doesn't return any error hence the meaningless values.
- badFilter := NewRequestFilter("FAIL", "-1")
+ badFilter := util.NewRequestFilter("FAIL", "-1")
m.rq.EXPECT().ListRequests(badFilter).Return([]ReqInfo{}, errors.New("foo bar: pizza failed"))
tests := []requestTest{
filterPath := "/api/v1/workers/list"
malformedJSONTest := testFromTempl(malformedJSONTestTempl, prefix, filterPath, methods...)
- validFilter := WorkersFilter{
+ validFilter := util.WorkersFilter{
Groups: Groups{"Lędzianie"},
Capabilities: map[string]string{"architecture": "AArch64"},
}
m.wm.EXPECT().ListWorkers(Groups{}, nil).Return(workers, nil)
m.wm.EXPECT().ListWorkers(nil, make(Capabilities)).Return(workers, nil)
- missingFilter := WorkersFilter{
+ missingFilter := util.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{
+ badFilter := util.WorkersFilter{
Groups: Groups{"Oops"},
}
m.wm.EXPECT().ListWorkers(badFilter.Groups, badFilter.Capabilities).Return([]WorkerInfo{}, errors.New("foo bar: pizza failed"))
name: prefix + "empty-filter",
path: filterPath,
methods: methods,
- json: string(jsonMustMarshal(WorkersFilter{nil, nil})),
+ json: string(jsonMustMarshal(util.WorkersFilter{nil, nil})),
contentType: contentTypeJSON,
status: http.StatusOK,
},
name: prefix + "empty2-filter",
path: filterPath,
methods: methods,
- json: string(jsonMustMarshal(WorkersFilter{nil, make(Capabilities)})),
+ json: string(jsonMustMarshal(util.WorkersFilter{nil, make(Capabilities)})),
contentType: contentTypeJSON,
status: http.StatusOK,
},
name: prefix + "empty3-filter",
path: filterPath,
methods: methods,
- json: string(jsonMustMarshal(WorkersFilter{Groups{}, nil})),
+ json: string(jsonMustMarshal(util.WorkersFilter{Groups{}, nil})),
contentType: contentTypeJSON,
status: http.StatusOK,
},
methods := []string{http.MethodPost}
notFoundTest := testFromTempl(notFoundTestTempl, prefix, fmt.Sprintf(path, missingUUID), methods...)
- notFoundTest.json = string(jsonMustMarshal(workerStatePack{IDLE}))
+ notFoundTest.json = string(jsonMustMarshal(util.WorkerStatePack{IDLE}))
malformedJSONTest := testFromTempl(malformedJSONTestTempl, prefix, fmt.Sprintf(path, validUUID), methods...)
missingErr := NotFoundError("Worker")
name: prefix + "valid",
path: fmt.Sprintf(path, validUUID),
methods: methods,
- json: string(jsonMustMarshal(workerStatePack{IDLE})),
+ json: string(jsonMustMarshal(util.WorkerStatePack{IDLE})),
contentType: contentTypeJSON,
status: http.StatusNoContent,
},
name: prefix + "bad-uuid",
path: fmt.Sprintf(path, invalidID),
methods: methods,
- json: string(jsonMustMarshal(workerStatePack{IDLE})),
+ json: string(jsonMustMarshal(util.WorkerStatePack{IDLE})),
contentType: contentTypeJSON,
status: http.StatusBadRequest,
},