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:
Maximilian Paß
2021-12-08 12:08:22 +01:00
committed by GitHub
parent c23a534f86
commit 1de559cebc
10 changed files with 173 additions and 3 deletions

View File

@ -6,6 +6,7 @@ import (
"github.com/openHPI/poseidon/internal/config"
"github.com/openHPI/poseidon/internal/environment"
"github.com/openHPI/poseidon/internal/runner"
"github.com/openHPI/poseidon/pkg/dto"
"github.com/openHPI/poseidon/pkg/logging"
"net/http"
)
@ -18,6 +19,7 @@ const (
VersionPath = "/version"
RunnersPath = "/runners"
EnvironmentsPath = "/execution-environments"
StatisticsPath = "/statistics"
)
// 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}
environmentController := &EnvironmentController{manager: environmentManager}
configureRoutes := func(router *mux.Router) {
runnerController.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() {
@ -73,3 +79,16 @@ func Version(writer http.ResponseWriter, _ *http.Request) {
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)
}
}

View File

@ -254,6 +254,10 @@ func (n *NomadEnvironment) DeleteRunner(id string) {
n.idleRunners.Delete(id)
}
func (n *NomadEnvironment) IdleRunnerCount() int {
return n.idleRunners.Length()
}
// MarshalJSON implements the json.Marshaler interface.
// This converts the NomadEnvironment into the expected schema for dto.ExecutionEnvironmentData.
func (n *NomadEnvironment) MarshalJSON() (res []byte, err error) {

View File

@ -44,6 +44,9 @@ type Manager interface {
// Delete removes the specified execution environment.
// Iff the specified environment could not be found Delete returns false.
Delete(id dto.EnvironmentID) (bool, error)
// Statistics returns statistical data for each execution environment.
Statistics() map[dto.EnvironmentID]*dto.StatisticalExecutionEnvironmentData
}
type NomadEnvironmentManager struct {
@ -156,6 +159,10 @@ func (m *NomadEnvironmentManager) Delete(id dto.EnvironmentID) (bool, error) {
return true, nil
}
func (m *NomadEnvironmentManager) Statistics() map[dto.EnvironmentID]*dto.StatisticalExecutionEnvironmentData {
return m.runnerManager.EnvironmentStatistics()
}
func (m *NomadEnvironmentManager) Load() error {
templateJobs, err := m.api.LoadEnvironmentJobs()
if err != nil {

View File

@ -115,3 +115,19 @@ func (_m *ManagerMock) Load() error {
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
}

View File

@ -66,6 +66,20 @@ func (_m *ExecutionEnvironmentMock) ID() dto.EnvironmentID {
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:
func (_m *ExecutionEnvironmentMock) Image() string {
ret := _m.Called()

View File

@ -62,6 +62,8 @@ type ExecutionEnvironment interface {
AddRunner(r Runner)
// DeleteRunner removes an idle runner from the environment.
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
@ -82,6 +84,9 @@ type Manager interface {
// It does nothing if the specified environment can not be found.
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.
// 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)
@ -136,6 +141,27 @@ func (m *NomadRunnerManager) DeleteEnvironment(id dto.EnvironmentID) {
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) {
environment, ok := m.environments.Get(environmentID)
if !ok {

View File

@ -40,6 +40,22 @@ func (_m *ManagerMock) DeleteEnvironment(id dto.EnvironmentID) {
_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
func (_m *ManagerMock) Get(runnerID string) (Runner, error) {
ret := _m.Called(runnerID)

View File

@ -6,6 +6,9 @@ import (
// Storage is an interface for storing runners.
type Storage interface {
// List returns all runners from the storage.
List() []Runner
// Add adds a runner to the storage.
// It overwrites the old runner if one with the same id was already stored.
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) {
s.Lock()
defer s.Unlock()