
We previously didn't really had any structure in our project apart from creating a new folder for each package in our project root. Now that we have accumulated some packages, we use the well-known Golang project layout in order to clearly communicate our intent with packages. See https://github.com/golang-standards/project-layout
133 lines
4.3 KiB
Go
133 lines
4.3 KiB
Go
package environment
|
|
|
|
import (
|
|
_ "embed"
|
|
"fmt"
|
|
nomadApi "github.com/hashicorp/nomad/api"
|
|
"github.com/hashicorp/nomad/jobspec2"
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
"gitlab.hpi.de/codeocean/codemoon/poseidon/internal/nomad"
|
|
"gitlab.hpi.de/codeocean/codemoon/poseidon/internal/runner"
|
|
"gitlab.hpi.de/codeocean/codemoon/poseidon/pkg/dto"
|
|
"gitlab.hpi.de/codeocean/codemoon/poseidon/pkg/logging"
|
|
"strconv"
|
|
)
|
|
|
|
// templateEnvironmentJobHCL holds our default job in HCL format.
|
|
// The default job is used when creating new job and provides
|
|
// common settings that all the jobs share.
|
|
//go:embed template-environment-job.hcl
|
|
var templateEnvironmentJobHCL string
|
|
|
|
var log = logging.GetLogger("environment")
|
|
|
|
// Manager encapsulates API calls to the executor API for creation and deletion of execution environments.
|
|
type Manager interface {
|
|
// Load fetches all already created execution environments from the executor and registers them at the runner manager.
|
|
// It should be called during the startup process (e.g. on creation of the Manager).
|
|
Load() error
|
|
|
|
// CreateOrUpdate creates/updates an execution environment on the executor.
|
|
// If the job was created, the returned boolean is true, if it was updated, it is false.
|
|
// If err is not nil, that means the environment was neither created nor updated.
|
|
CreateOrUpdate(
|
|
id runner.EnvironmentID,
|
|
request dto.ExecutionEnvironmentRequest,
|
|
) (bool, error)
|
|
}
|
|
|
|
func NewNomadEnvironmentManager(
|
|
runnerManager runner.Manager,
|
|
apiClient nomad.ExecutorAPI,
|
|
) *NomadEnvironmentManager {
|
|
m := &NomadEnvironmentManager{runnerManager, apiClient, *parseJob(templateEnvironmentJobHCL)}
|
|
if err := m.Load(); err != nil {
|
|
log.WithError(err).Error("Error recovering the execution environments")
|
|
}
|
|
runnerManager.Load()
|
|
return m
|
|
}
|
|
|
|
type NomadEnvironmentManager struct {
|
|
runnerManager runner.Manager
|
|
api nomad.ExecutorAPI
|
|
templateEnvironmentJob nomadApi.Job
|
|
}
|
|
|
|
func (m *NomadEnvironmentManager) CreateOrUpdate(
|
|
id runner.EnvironmentID,
|
|
request dto.ExecutionEnvironmentRequest,
|
|
) (bool, error) {
|
|
templateJob, err := m.api.RegisterTemplateJob(&m.templateEnvironmentJob, runner.TemplateJobID(id),
|
|
request.PrewarmingPoolSize, request.CPULimit, request.MemoryLimit,
|
|
request.Image, request.NetworkAccess, request.ExposedPorts)
|
|
|
|
if err != nil {
|
|
return false, fmt.Errorf("error registering template job in API: %w", err)
|
|
}
|
|
|
|
created, err := m.runnerManager.CreateOrUpdateEnvironment(id, request.PrewarmingPoolSize, templateJob, true)
|
|
if err != nil {
|
|
return created, fmt.Errorf("error updating environment in runner manager: %w", err)
|
|
}
|
|
return created, nil
|
|
}
|
|
|
|
func (m *NomadEnvironmentManager) Load() error {
|
|
templateJobs, err := m.api.LoadEnvironmentJobs()
|
|
if err != nil {
|
|
return fmt.Errorf("couldn't load template jobs: %w", err)
|
|
}
|
|
|
|
for _, job := range templateJobs {
|
|
jobLogger := log.WithField("jobID", *job.ID)
|
|
if *job.Status != structs.JobStatusRunning {
|
|
jobLogger.Info("Job not running, skipping ...")
|
|
continue
|
|
}
|
|
configTaskGroup := nomad.FindConfigTaskGroup(job)
|
|
if configTaskGroup == nil {
|
|
jobLogger.Info("Couldn't find config task group in job, skipping ...")
|
|
continue
|
|
}
|
|
desiredIdleRunnersCount, err := strconv.Atoi(configTaskGroup.Meta[nomad.ConfigMetaPoolSizeKey])
|
|
if err != nil {
|
|
jobLogger.Infof("Couldn't convert pool size to int: %v, skipping ...", err)
|
|
continue
|
|
}
|
|
environmentIDString, err := runner.EnvironmentIDFromTemplateJobID(*job.ID)
|
|
if err != nil {
|
|
jobLogger.WithError(err).Error("Couldn't retrieve environment id from template job")
|
|
}
|
|
environmentID, err := runner.NewEnvironmentID(environmentIDString)
|
|
if err != nil {
|
|
jobLogger.WithField("environmentID", environmentIDString).
|
|
WithError(err).
|
|
Error("Couldn't retrieve environmentID from string")
|
|
continue
|
|
}
|
|
_, err = m.runnerManager.CreateOrUpdateEnvironment(environmentID, uint(desiredIdleRunnersCount), job, false)
|
|
if err != nil {
|
|
jobLogger.WithError(err).Info("Could not recover job.")
|
|
continue
|
|
}
|
|
jobLogger.Info("Successfully recovered environment")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func parseJob(jobHCL string) *nomadApi.Job {
|
|
config := jobspec2.ParseConfig{
|
|
Body: []byte(jobHCL),
|
|
AllowFS: false,
|
|
Strict: true,
|
|
}
|
|
job, err := jobspec2.ParseWithConfig(&config)
|
|
if err != nil {
|
|
log.WithError(err).Fatal("Error parsing Nomad job")
|
|
return nil
|
|
}
|
|
|
|
return job
|
|
}
|