Add basic nomad integration and runner provider
This commit is contained in:
33
api/api.go
33
api/api.go
@ -3,12 +3,19 @@ package api
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gorilla/mux"
|
||||
"gitlab.hpi.de/codeocean/codemoon/poseidon/api/dto"
|
||||
"gitlab.hpi.de/codeocean/codemoon/poseidon/logging"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var log = logging.GetLogger("api")
|
||||
|
||||
const (
|
||||
RouteBase = "/api/v1"
|
||||
RouteHealth = "/health"
|
||||
RouteRunners = "/runners"
|
||||
)
|
||||
|
||||
// NewRouter returns an HTTP handler (http.Handler) which can be
|
||||
// used by the net/http package to serve the routes of our API. It
|
||||
// always returns a router for the newest version of our API. We
|
||||
@ -23,20 +30,34 @@ func NewRouter() http.Handler {
|
||||
return router
|
||||
}
|
||||
|
||||
// newRouterV1 returns a subrouter containing the routes of version
|
||||
// newRouterV1 returns a sub-router containing the routes of version
|
||||
// 1 of our API.
|
||||
func newRouterV1(router *mux.Router) *mux.Router {
|
||||
v1 := router.PathPrefix("/api/v1").Subrouter()
|
||||
v1.HandleFunc("/health", Health).Methods(http.MethodGet)
|
||||
v1 := router.PathPrefix(RouteBase).Subrouter()
|
||||
v1.HandleFunc(RouteHealth, Health).Methods(http.MethodGet)
|
||||
registerRunnerRoutes(v1.PathPrefix(RouteRunners).Subrouter())
|
||||
return v1
|
||||
}
|
||||
|
||||
func writeJson(writer http.ResponseWriter, content interface{}) {
|
||||
func writeInternalServerError(writer http.ResponseWriter, err error, errorCode dto.ErrorCode) {
|
||||
sendJson(writer, &dto.InternalServerError{Message: err.Error(), ErrorCode: errorCode}, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
func writeBadRequest(writer http.ResponseWriter, err error) {
|
||||
sendJson(writer, &dto.ClientError{Message: err.Error()}, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
func writeNotFound(writer http.ResponseWriter, err error) {
|
||||
sendJson(writer, &dto.ClientError{Message: err.Error()}, http.StatusNotFound)
|
||||
}
|
||||
|
||||
func sendJson(writer http.ResponseWriter, content interface{}, httpStatusCode int) {
|
||||
writer.Header().Set("Content-Type", "application/json")
|
||||
writer.WriteHeader(httpStatusCode)
|
||||
response, err := json.Marshal(content)
|
||||
if err != nil {
|
||||
log.WithError(err).Warn("JSON marshal error")
|
||||
http.Error(writer, err.Error(), http.StatusInternalServerError)
|
||||
// cannot produce infinite recursive loop, since json.Marshal of dto.InternalServerError won't return an error
|
||||
writeInternalServerError(writer, err, dto.ErrorUnknown)
|
||||
return
|
||||
}
|
||||
if _, err := writer.Write(response); err != nil {
|
||||
|
33
api/dto/dto.go
Normal file
33
api/dto/dto.go
Normal file
@ -0,0 +1,33 @@
|
||||
package dto
|
||||
|
||||
// RequestRunner is the expected json structure of the request body for the ProvideRunner function
|
||||
type RequestRunner struct {
|
||||
ExecutionEnvironmentId int `json:"executionEnvironmentId"`
|
||||
InactivityTimeout int `json:"inactivityTimeout"`
|
||||
}
|
||||
|
||||
// ResponseRunner is the expected result from the api server
|
||||
type ResponseRunner struct {
|
||||
Id string `json:"runnerId"`
|
||||
}
|
||||
|
||||
// ClientError is the response interface if the request is not valid
|
||||
type ClientError struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// InternalServerError is the response interface that is returned when an error occurs
|
||||
type InternalServerError struct {
|
||||
Message string `json:"message"`
|
||||
ErrorCode ErrorCode `json:"errorCode"`
|
||||
}
|
||||
|
||||
// ErrorCode is the type for error codes expected by CodeOcean
|
||||
type ErrorCode string
|
||||
|
||||
const (
|
||||
ErrorNomadUnreachable ErrorCode = "NOMAD_UNREACHABLE"
|
||||
ErrorNomadOverload ErrorCode = "NOMAD_OVERLOAD"
|
||||
ErrorNomadInternalServerError ErrorCode = "NOMAD_INTERNAL_SERVER_ERROR"
|
||||
ErrorUnknown ErrorCode = "UNKNOWN"
|
||||
)
|
@ -7,8 +7,5 @@ import (
|
||||
// Health tries to respond that the server is alive.
|
||||
// If it is not, the response won't reach the client.
|
||||
func Health(writer http.ResponseWriter, _ *http.Request) {
|
||||
writeJson(
|
||||
writer,
|
||||
Message{"I'm alive!"},
|
||||
)
|
||||
writer.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@ -15,10 +14,5 @@ func TestHealthRoute(t *testing.T) {
|
||||
}
|
||||
recorder := httptest.NewRecorder()
|
||||
http.HandlerFunc(Health).ServeHTTP(recorder, request)
|
||||
result := Message{}
|
||||
_ = json.Unmarshal(recorder.Body.Bytes(), &result)
|
||||
|
||||
assert.Equal(t, http.StatusOK, recorder.Code)
|
||||
assert.Equal(t, "application/json", recorder.Header().Get("Content-Type"))
|
||||
assert.Equal(t, "I'm alive!", result.Message)
|
||||
assert.Equal(t, http.StatusNoContent, recorder.Code)
|
||||
}
|
||||
|
@ -1,5 +0,0 @@
|
||||
package api
|
||||
|
||||
type Message struct {
|
||||
Message string `json:"msg"`
|
||||
}
|
35
api/runners.go
Normal file
35
api/runners.go
Normal file
@ -0,0 +1,35 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gorilla/mux"
|
||||
"gitlab.hpi.de/codeocean/codemoon/poseidon/api/dto"
|
||||
"gitlab.hpi.de/codeocean/codemoon/poseidon/environment"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// ProvideRunner tries to respond with the id of a runner.
|
||||
// This runner is then reserved for future use.
|
||||
func ProvideRunner(writer http.ResponseWriter, request *http.Request) {
|
||||
runnerRequest := new(dto.RequestRunner)
|
||||
if err := json.NewDecoder(request.Body).Decode(runnerRequest); err != nil {
|
||||
writeBadRequest(writer, err)
|
||||
return
|
||||
}
|
||||
environment, err := environment.GetExecutionEnvironment(runnerRequest.ExecutionEnvironmentId)
|
||||
if err != nil {
|
||||
writeNotFound(writer, err)
|
||||
return
|
||||
}
|
||||
runner, err := environment.NextRunner()
|
||||
if err != nil {
|
||||
writeInternalServerError(writer, err, dto.ErrorNomadOverload)
|
||||
return
|
||||
}
|
||||
|
||||
sendJson(writer, &dto.ResponseRunner{Id: runner.Id()}, http.StatusOK)
|
||||
}
|
||||
|
||||
func registerRunnerRoutes(router *mux.Router) {
|
||||
router.HandleFunc("", ProvideRunner).Methods(http.MethodPost)
|
||||
}
|
Reference in New Issue
Block a user