Add statistics route for execution environments
* Add statistics route for execution environments * Add maximum to port api definition Co-authored-by: Sebastian Serth <MrSerth@users.noreply.github.com>
This commit is contained in:
@ -120,6 +120,51 @@ paths:
|
|||||||
"500":
|
"500":
|
||||||
$ref: "#/components/responses/InternalServerError"
|
$ref: "#/components/responses/InternalServerError"
|
||||||
|
|
||||||
|
/statistics/execution-environments:
|
||||||
|
get:
|
||||||
|
summary: Retrieve the statistics about the execution environments of Poseidon
|
||||||
|
description: Return Return the current availability and usage of runners.
|
||||||
|
tags:
|
||||||
|
- miscellaneous
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Success. Returns all execution environments
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
description: The id of the execution environment.
|
||||||
|
type: integer
|
||||||
|
prewarmingPoolSize:
|
||||||
|
description: Number of runners with this configuration to prewarm.
|
||||||
|
type: integer
|
||||||
|
example: 50
|
||||||
|
idleRunners:
|
||||||
|
description: Number of runners currently prewarmed.
|
||||||
|
type: number
|
||||||
|
example: 45
|
||||||
|
usedRunners:
|
||||||
|
description: Number of runners currently in use.
|
||||||
|
type: number
|
||||||
|
example: 20
|
||||||
|
example:
|
||||||
|
21:
|
||||||
|
id: 21
|
||||||
|
prewarmingPoolSize: 50
|
||||||
|
idleRunners: 45
|
||||||
|
usedRunners: 20
|
||||||
|
42:
|
||||||
|
id: 42
|
||||||
|
prewarmingPoolSize: 50
|
||||||
|
idleRunners: 45
|
||||||
|
usedRunners: 20
|
||||||
|
"500":
|
||||||
|
$ref: "#/components/responses/InternalServerError"
|
||||||
|
|
||||||
/runners:
|
/runners:
|
||||||
post:
|
post:
|
||||||
summary: Provide a runner
|
summary: Provide a runner
|
||||||
@ -144,7 +189,7 @@ paths:
|
|||||||
type: integer
|
type: integer
|
||||||
example: 6
|
example: 6
|
||||||
required:
|
required:
|
||||||
- executionEnvironment
|
- executionEnvironmentId
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
@ -167,7 +212,9 @@ paths:
|
|||||||
properties:
|
properties:
|
||||||
exposedPort:
|
exposedPort:
|
||||||
description: The port inside the container.
|
description: The port inside the container.
|
||||||
type: uint
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
maximum: 65535
|
||||||
example: 80
|
example: 80
|
||||||
hostAddress:
|
hostAddress:
|
||||||
description: The address which can be contacted to reach the mapped port.
|
description: The address which can be contacted to reach the mapped port.
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"github.com/openHPI/poseidon/internal/config"
|
"github.com/openHPI/poseidon/internal/config"
|
||||||
"github.com/openHPI/poseidon/internal/environment"
|
"github.com/openHPI/poseidon/internal/environment"
|
||||||
"github.com/openHPI/poseidon/internal/runner"
|
"github.com/openHPI/poseidon/internal/runner"
|
||||||
|
"github.com/openHPI/poseidon/pkg/dto"
|
||||||
"github.com/openHPI/poseidon/pkg/logging"
|
"github.com/openHPI/poseidon/pkg/logging"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
@ -18,6 +19,7 @@ const (
|
|||||||
VersionPath = "/version"
|
VersionPath = "/version"
|
||||||
RunnersPath = "/runners"
|
RunnersPath = "/runners"
|
||||||
EnvironmentsPath = "/execution-environments"
|
EnvironmentsPath = "/execution-environments"
|
||||||
|
StatisticsPath = "/statistics"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewRouter returns a *mux.Router which can be
|
// NewRouter returns a *mux.Router which can be
|
||||||
@ -46,10 +48,14 @@ func configureV1Router(router *mux.Router, runnerManager runner.Manager, environ
|
|||||||
|
|
||||||
runnerController := &RunnerController{manager: runnerManager}
|
runnerController := &RunnerController{manager: runnerManager}
|
||||||
environmentController := &EnvironmentController{manager: environmentManager}
|
environmentController := &EnvironmentController{manager: environmentManager}
|
||||||
|
|
||||||
configureRoutes := func(router *mux.Router) {
|
configureRoutes := func(router *mux.Router) {
|
||||||
runnerController.ConfigureRoutes(router)
|
runnerController.ConfigureRoutes(router)
|
||||||
environmentController.ConfigureRoutes(router)
|
environmentController.ConfigureRoutes(router)
|
||||||
|
|
||||||
|
// May add a statistics controller if another route joins
|
||||||
|
statisticsRouter := router.PathPrefix(StatisticsPath).Subrouter()
|
||||||
|
statisticsRouter.
|
||||||
|
HandleFunc(EnvironmentsPath, StatisticsExecutionEnvironments(environmentManager)).Methods(http.MethodGet)
|
||||||
}
|
}
|
||||||
|
|
||||||
if auth.InitializeAuthentication() {
|
if auth.InitializeAuthentication() {
|
||||||
@ -73,3 +79,16 @@ func Version(writer http.ResponseWriter, _ *http.Request) {
|
|||||||
writer.WriteHeader(http.StatusNotFound)
|
writer.WriteHeader(http.StatusNotFound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StatisticsExecutionEnvironments handles the route for statistics about execution environments.
|
||||||
|
// It responds the prewarming pool size and the number of idle runners and used runners.
|
||||||
|
func StatisticsExecutionEnvironments(manager environment.Manager) http.HandlerFunc {
|
||||||
|
return func(writer http.ResponseWriter, _ *http.Request) {
|
||||||
|
result := make(map[string]*dto.StatisticalExecutionEnvironmentData)
|
||||||
|
environmentsData := manager.Statistics()
|
||||||
|
for id, data := range environmentsData {
|
||||||
|
result[id.ToString()] = data
|
||||||
|
}
|
||||||
|
sendJSON(writer, result, http.StatusOK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -254,6 +254,10 @@ func (n *NomadEnvironment) DeleteRunner(id string) {
|
|||||||
n.idleRunners.Delete(id)
|
n.idleRunners.Delete(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *NomadEnvironment) IdleRunnerCount() int {
|
||||||
|
return n.idleRunners.Length()
|
||||||
|
}
|
||||||
|
|
||||||
// MarshalJSON implements the json.Marshaler interface.
|
// MarshalJSON implements the json.Marshaler interface.
|
||||||
// This converts the NomadEnvironment into the expected schema for dto.ExecutionEnvironmentData.
|
// This converts the NomadEnvironment into the expected schema for dto.ExecutionEnvironmentData.
|
||||||
func (n *NomadEnvironment) MarshalJSON() (res []byte, err error) {
|
func (n *NomadEnvironment) MarshalJSON() (res []byte, err error) {
|
||||||
|
@ -44,6 +44,9 @@ type Manager interface {
|
|||||||
// Delete removes the specified execution environment.
|
// Delete removes the specified execution environment.
|
||||||
// Iff the specified environment could not be found Delete returns false.
|
// Iff the specified environment could not be found Delete returns false.
|
||||||
Delete(id dto.EnvironmentID) (bool, error)
|
Delete(id dto.EnvironmentID) (bool, error)
|
||||||
|
|
||||||
|
// Statistics returns statistical data for each execution environment.
|
||||||
|
Statistics() map[dto.EnvironmentID]*dto.StatisticalExecutionEnvironmentData
|
||||||
}
|
}
|
||||||
|
|
||||||
type NomadEnvironmentManager struct {
|
type NomadEnvironmentManager struct {
|
||||||
@ -156,6 +159,10 @@ func (m *NomadEnvironmentManager) Delete(id dto.EnvironmentID) (bool, error) {
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *NomadEnvironmentManager) Statistics() map[dto.EnvironmentID]*dto.StatisticalExecutionEnvironmentData {
|
||||||
|
return m.runnerManager.EnvironmentStatistics()
|
||||||
|
}
|
||||||
|
|
||||||
func (m *NomadEnvironmentManager) Load() error {
|
func (m *NomadEnvironmentManager) Load() error {
|
||||||
templateJobs, err := m.api.LoadEnvironmentJobs()
|
templateJobs, err := m.api.LoadEnvironmentJobs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -115,3 +115,19 @@ func (_m *ManagerMock) Load() error {
|
|||||||
|
|
||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Statistics provides a mock function with given fields:
|
||||||
|
func (_m *ManagerMock) Statistics() map[dto.EnvironmentID]*dto.StatisticalExecutionEnvironmentData {
|
||||||
|
ret := _m.Called()
|
||||||
|
|
||||||
|
var r0 map[dto.EnvironmentID]*dto.StatisticalExecutionEnvironmentData
|
||||||
|
if rf, ok := ret.Get(0).(func() map[dto.EnvironmentID]*dto.StatisticalExecutionEnvironmentData); ok {
|
||||||
|
r0 = rf()
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(map[dto.EnvironmentID]*dto.StatisticalExecutionEnvironmentData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
@ -66,6 +66,20 @@ func (_m *ExecutionEnvironmentMock) ID() dto.EnvironmentID {
|
|||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IdleRunnerCount provides a mock function with given fields:
|
||||||
|
func (_m *ExecutionEnvironmentMock) IdleRunnerCount() int {
|
||||||
|
ret := _m.Called()
|
||||||
|
|
||||||
|
var r0 int
|
||||||
|
if rf, ok := ret.Get(0).(func() int); ok {
|
||||||
|
r0 = rf()
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
// Image provides a mock function with given fields:
|
// Image provides a mock function with given fields:
|
||||||
func (_m *ExecutionEnvironmentMock) Image() string {
|
func (_m *ExecutionEnvironmentMock) Image() string {
|
||||||
ret := _m.Called()
|
ret := _m.Called()
|
||||||
|
@ -62,6 +62,8 @@ type ExecutionEnvironment interface {
|
|||||||
AddRunner(r Runner)
|
AddRunner(r Runner)
|
||||||
// DeleteRunner removes an idle runner from the environment.
|
// DeleteRunner removes an idle runner from the environment.
|
||||||
DeleteRunner(id string)
|
DeleteRunner(id string)
|
||||||
|
// IdleRunnerCount returns the number of idle runners of the environment.
|
||||||
|
IdleRunnerCount() int
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manager keeps track of the used and unused runners of all execution environments in order to provide unused
|
// Manager keeps track of the used and unused runners of all execution environments in order to provide unused
|
||||||
@ -82,6 +84,9 @@ type Manager interface {
|
|||||||
// It does nothing if the specified environment can not be found.
|
// It does nothing if the specified environment can not be found.
|
||||||
DeleteEnvironment(id dto.EnvironmentID)
|
DeleteEnvironment(id dto.EnvironmentID)
|
||||||
|
|
||||||
|
// EnvironmentStatistics returns statistical data for each execution environment.
|
||||||
|
EnvironmentStatistics() map[dto.EnvironmentID]*dto.StatisticalExecutionEnvironmentData
|
||||||
|
|
||||||
// Claim returns a new runner. The runner is deleted after duration seconds if duration is not 0.
|
// Claim returns a new runner. The runner is deleted after duration seconds if duration is not 0.
|
||||||
// It makes sure that the runner is not in use yet and returns an error if no runner could be provided.
|
// It makes sure that the runner is not in use yet and returns an error if no runner could be provided.
|
||||||
Claim(id dto.EnvironmentID, duration int) (Runner, error)
|
Claim(id dto.EnvironmentID, duration int) (Runner, error)
|
||||||
@ -136,6 +141,27 @@ func (m *NomadRunnerManager) DeleteEnvironment(id dto.EnvironmentID) {
|
|||||||
m.environments.Delete(id)
|
m.environments.Delete(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *NomadRunnerManager) EnvironmentStatistics() map[dto.EnvironmentID]*dto.StatisticalExecutionEnvironmentData {
|
||||||
|
environments := make(map[dto.EnvironmentID]*dto.StatisticalExecutionEnvironmentData)
|
||||||
|
for _, e := range m.environments.List() {
|
||||||
|
environments[e.ID()] = &dto.StatisticalExecutionEnvironmentData{
|
||||||
|
ID: int(e.ID()),
|
||||||
|
PrewarmingPoolSize: e.PrewarmingPoolSize(),
|
||||||
|
IdleRunners: uint(e.IdleRunnerCount()),
|
||||||
|
UsedRunners: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, r := range m.usedRunners.List() {
|
||||||
|
id, err := nomad.EnvironmentIDFromRunnerID(r.ID())
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Error("Stored runners must have correct IDs")
|
||||||
|
}
|
||||||
|
environments[id].UsedRunners++
|
||||||
|
}
|
||||||
|
return environments
|
||||||
|
}
|
||||||
|
|
||||||
func (m *NomadRunnerManager) Claim(environmentID dto.EnvironmentID, duration int) (Runner, error) {
|
func (m *NomadRunnerManager) Claim(environmentID dto.EnvironmentID, duration int) (Runner, error) {
|
||||||
environment, ok := m.environments.Get(environmentID)
|
environment, ok := m.environments.Get(environmentID)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -40,6 +40,22 @@ func (_m *ManagerMock) DeleteEnvironment(id dto.EnvironmentID) {
|
|||||||
_m.Called(id)
|
_m.Called(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EnvironmentStatistics provides a mock function with given fields:
|
||||||
|
func (_m *ManagerMock) EnvironmentStatistics() map[dto.EnvironmentID]*dto.StatisticalExecutionEnvironmentData {
|
||||||
|
ret := _m.Called()
|
||||||
|
|
||||||
|
var r0 map[dto.EnvironmentID]*dto.StatisticalExecutionEnvironmentData
|
||||||
|
if rf, ok := ret.Get(0).(func() map[dto.EnvironmentID]*dto.StatisticalExecutionEnvironmentData); ok {
|
||||||
|
r0 = rf()
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(map[dto.EnvironmentID]*dto.StatisticalExecutionEnvironmentData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
// Get provides a mock function with given fields: runnerID
|
// Get provides a mock function with given fields: runnerID
|
||||||
func (_m *ManagerMock) Get(runnerID string) (Runner, error) {
|
func (_m *ManagerMock) Get(runnerID string) (Runner, error) {
|
||||||
ret := _m.Called(runnerID)
|
ret := _m.Called(runnerID)
|
||||||
|
@ -6,6 +6,9 @@ import (
|
|||||||
|
|
||||||
// Storage is an interface for storing runners.
|
// Storage is an interface for storing runners.
|
||||||
type Storage interface {
|
type Storage interface {
|
||||||
|
// List returns all runners from the storage.
|
||||||
|
List() []Runner
|
||||||
|
|
||||||
// Add adds a runner to the storage.
|
// Add adds a runner to the storage.
|
||||||
// It overwrites the old runner if one with the same id was already stored.
|
// It overwrites the old runner if one with the same id was already stored.
|
||||||
Add(Runner)
|
Add(Runner)
|
||||||
@ -41,6 +44,15 @@ func NewLocalRunnerStorage() *localRunnerStorage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *localRunnerStorage) List() (r []Runner) {
|
||||||
|
s.RLock()
|
||||||
|
defer s.RUnlock()
|
||||||
|
for _, value := range s.runners {
|
||||||
|
r = append(r, value)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
func (s *localRunnerStorage) Add(r Runner) {
|
func (s *localRunnerStorage) Add(r Runner) {
|
||||||
s.Lock()
|
s.Lock()
|
||||||
defer s.Unlock()
|
defer s.Unlock()
|
||||||
|
@ -53,6 +53,15 @@ type ExecutionEnvironmentData struct {
|
|||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StatisticalExecutionEnvironmentData is the expected json structure of the response body
|
||||||
|
// for routes returning statistics about execution environments.
|
||||||
|
type StatisticalExecutionEnvironmentData struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
PrewarmingPoolSize uint `json:"prewarmingPoolSize"`
|
||||||
|
IdleRunners uint `json:"idleRunners"`
|
||||||
|
UsedRunners uint `json:"usedRunners"`
|
||||||
|
}
|
||||||
|
|
||||||
// ExecutionEnvironmentRequest is the expected json structure of the request body
|
// ExecutionEnvironmentRequest is the expected json structure of the request body
|
||||||
// for the create execution environment function.
|
// for the create execution environment function.
|
||||||
type ExecutionEnvironmentRequest struct {
|
type ExecutionEnvironmentRequest struct {
|
||||||
|
Reference in New Issue
Block a user