| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222 |
- package executor
- import (
- "context"
- "errors"
- "fmt"
- "time"
- dockerpkg "git.buran.team/main/cep/docker"
- schemepkg "git.buran.team/main/cep/scheme"
- fw "git.buran.team/main/fairwind"
- )
- var ErrContainerContextCancelled = errors.New("container context cancelled")
- var ErrContainerContextTimeout = errors.New("container context timeout")
- var ErrContainerResultInvalid = errors.New("container result invalid")
- // TODO: add start/stop conditions
- type OneshotContainer struct {
- global *Global
- ticket *Ticket
- id string
- container *dockerpkg.Container
- timeout int
- result *Result
- }
- func NewOneshotContainer(
- global *Global,
- ticket *Ticket,
- network *Network,
- containerIndex int,
- containerScheme schemepkg.Container,
- ) (*OneshotContainer, error) {
- varialbles := []dockerpkg.Variable{}
- for _, variable := range containerScheme.Variables {
- varialbles = append(
- varialbles,
- dockerpkg.Variable{
- Key: variable.Key,
- Value: variable.Value,
- },
- )
- }
- hosts := []dockerpkg.ContainerSettingsHost{}
- for _, host := range network.Hosts() {
- hosts = append(
- hosts,
- dockerpkg.ContainerSettingsHost{
- Name: host.Name,
- IP: host.IP,
- },
- )
- }
- container := dockerpkg.NewContainer(
- global.Ctx,
- global.Log,
- global.Docker,
- network.Network(),
- dockerpkg.NewImage(
- global.Ctx,
- global.Log,
- global.Docker,
- global.Registry,
- containerScheme.Image.Tag,
- containerScheme.Image.Version,
- containerScheme.Image.Pull,
- ),
- dockerpkg.ContainerSettings{
- Name: containerName(
- ticket.Index,
- containerIndex,
- ),
- Command: containerScheme.Command,
- Variables: varialbles,
- Hosts: hosts,
- Binds: containerScheme.Binds,
- Permissions: dockerpkg.ContainerSettingsPermissions{
- Privileged: containerScheme.Permissions.Privileged,
- Capabilities: containerScheme.Permissions.Capabilities,
- },
- Network: dockerpkg.ContainerSettingsNetwork{
- IP: containerIP(
- ticket.Index,
- containerIndex,
- ),
- },
- Resources: dockerpkg.ContainerResources{},
- },
- )
- result, err := NewResult(containerScheme.KindOneshot.Result)
- if err != nil {
- return nil, fmt.Errorf("can't create oneshot container: %w", err)
- }
- return &OneshotContainer{
- global: global,
- ticket: ticket,
- id: containerScheme.ID,
- container: container,
- timeout: containerScheme.KindOneshot.Timeout,
- result: result,
- }, nil
- }
- func (this *OneshotContainer) ID() string {
- return this.id
- }
- func (this *OneshotContainer) Start() *ContainerResult {
- var err error
- this.global.Log.Debug("starting container", fw.LogValue("uuid", this.ticket.UUID), fw.LogValue("id", this.id))
- defer func() {
- if err != nil {
- this.global.Log.Debug("container start failed", fw.LogValue("uuid", this.ticket.UUID), fw.LogValue("id", this.id), fw.LogError(err))
- } else {
- this.global.Log.Debug("container started", fw.LogValue("uuid", this.ticket.UUID), fw.LogValue("id", this.id))
- }
- }()
- // Start
- err = this.container.Start()
- if err != nil {
- return NewContainerResultError(
- fmt.Errorf("can't start container: %w", err),
- )
- }
- // Wait
- ctx, cancel := context.WithTimeout(
- context.Background(),
- time.Duration(this.timeout)*time.Millisecond,
- )
- defer cancel()
- var status dockerpkg.ExecutionResult
- for {
- select {
- case <-this.global.Ctx.Done():
- err = ErrContainerContextCancelled
- return NewContainerResultError(err)
- case <-ctx.Done():
- err = ErrContainerContextTimeout
- return NewContainerResultError(err)
- case <-time.After(100 * time.Millisecond): // NOTE: basic 100-millisecond startup delay
- }
- this.global.Log.Debug("check container status", fw.LogValue("uuid", this.ticket.UUID), fw.LogValue("id", this.id))
- status = this.container.Status(true)
- if status.Error != nil {
- this.global.Log.Information("container startup error", fw.LogError(status.Error))
- continue
- }
- this.global.Log.Debug("check container result", fw.LogValue("uuid", this.ticket.UUID), fw.LogValue("id", this.id))
- if !this.result.Check(
- status.Code,
- status.Stdout,
- status.Stderr,
- ) {
- this.global.Log.Information("container startup check failed", fw.LogValue("uuid", this.ticket.UUID), fw.LogValue("id", this.id))
- err = ErrContainerResultInvalid
- return NewContainerResultError(err)
- }
- this.global.Log.Debug("container checks succeed", fw.LogValue("uuid", this.ticket.UUID), fw.LogValue("id", this.id))
- break
- }
- // Done
- return NewContainerResultSuccess(
- map[string]bool{},
- status.Code,
- status.Stdout,
- status.Stderr,
- )
- }
- func (this *OneshotContainer) Stop() *ContainerResult {
- var err error
- this.global.Log.Debug("stopping container", fw.LogValue("uuid", this.ticket.UUID), fw.LogValue("id", this.id))
- defer func() {
- if err != nil {
- this.global.Log.Debug("container stop failed", fw.LogValue("uuid", this.ticket.UUID), fw.LogValue("id", this.id), fw.LogError(err))
- } else {
- this.global.Log.Debug("container stopped", fw.LogValue("uuid", this.ticket.UUID), fw.LogValue("id", this.id))
- }
- }()
- err = this.container.Stop()
- if err != nil {
- return NewContainerResultError(
- fmt.Errorf("can't stop container: %w", err),
- )
- }
- return NewContainerResultSuccess(
- map[string]bool{},
- 0,
- []byte{},
- []byte{},
- )
- }
- func (this *OneshotContainer) Execute(timeout int, command string) ExecutionResult {
- result := this.container.Execute(timeout, command)
- return ExecutionResult{
- Error: result.Error,
- Code: result.Code,
- Stdout: result.Stdout,
- Stderr: result.Stderr,
- }
- }
- func (this *OneshotContainer) Read(timeout int, path string) ([]byte, error) {
- return this.container.Read(timeout, path)
- }
|