Files
cocos/agent/algorithm/docker/docker.go
T
Sammy Kerata Oina 22c14cb438
CI / lint (push) Has been cancelled
CI / checkproto (push) Has been cancelled
CI / test (agent) (push) Has been cancelled
CI / upload-coverage (push) Has been cancelled
CI / test (cli) (push) Has been cancelled
CI / test (cmd) (push) Has been cancelled
CI / test (internal) (push) Has been cancelled
CI / test (manager, true) (push) Has been cancelled
CI / test (pkg) (push) Has been cancelled
NOISSUE - Update protobuf generated files and improve algorithm handling in runner service (#612)
* Update protobuf generated files and improve algorithm handling in runner service

- Bump protoc version from v7.35.0 to v7.35.1 in generated protobuf files.
- Refactor RunRequest message in runner.proto to use string paths for algorithm and requirements instead of byte arrays.
- Update runner service to handle algorithm and requirements paths, removing the need for temporary file creation.
- Enhance error handling for missing algorithm paths in the runner service.
- Modify tests to align with the new RunRequest structure and ensure proper file handling.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* chore: update protoc version to 35.1 in CI workflow

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* feat: update systemd service configurations and dependencies for improved service management

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* fix: adjust AlgoWorkingDir handling in tests for Docker algorithm

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

---------

Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2026-07-28 13:54:43 +02:00

195 lines
5.3 KiB
Go

// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package docker
import (
"bufio"
"context"
"fmt"
"io"
"log/slog"
"os"
"path"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/mount"
"github.com/docker/docker/client"
"github.com/ultravioletrs/cocos/agent/algorithm"
"github.com/ultravioletrs/cocos/agent/algorithm/logging"
"github.com/ultravioletrs/cocos/agent/events"
)
const (
containerName = "agent_container"
datasetsMountPath = "/cocos/datasets"
resultsMountPath = "/cocos/results"
)
var _ algorithm.Algorithm = (*docker)(nil)
type docker struct {
algoFile string
logger *slog.Logger
stderr io.Writer
stdout io.Writer
cmpID string
}
func NewAlgorithm(logger *slog.Logger, eventsSvc events.Service, algoFile, cmpID string) algorithm.Algorithm {
d := &docker{
algoFile: algoFile,
logger: logger,
stderr: &logging.Stderr{Logger: logger, EventSvc: eventsSvc, CmpID: cmpID},
stdout: &logging.Stdout{Logger: logger},
cmpID: cmpID,
}
return d
}
func (d *docker) Run() error {
// Create a new Docker client.
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
return fmt.Errorf("could not create a new Docker client: %v", err)
}
// Open the Docker image tar file.
imageFile, err := os.Open(d.algoFile)
if err != nil {
return fmt.Errorf("could not open Docker image: %v", err)
}
defer imageFile.Close()
ctx := context.Background()
// Load the Docker image from the tar file.
resp, err := cli.ImageLoad(ctx, imageFile, client.ImageLoadWithQuiet(true))
if err != nil {
return fmt.Errorf("could not load Docker image from file: %v", err)
}
defer resp.Body.Close()
// List the loaded images to get the image ID.
images, err := cli.ImageList(ctx, image.ListOptions{})
if err != nil {
return fmt.Errorf("could not get the Docker image list: %v", err)
}
var imageID string = ""
var dockerImageName string = ""
for _, image := range images {
for _, tag := range image.RepoTags {
imageID = image.ID
dockerImageName = tag
break
}
}
if imageID == "" {
return fmt.Errorf("could not find image ID")
}
datasetsDir := path.Join(algorithm.AlgoWorkingDir, algorithm.DatasetsDir)
resultsDir := path.Join(algorithm.AlgoWorkingDir, algorithm.ResultsDir)
if err := os.MkdirAll(datasetsDir, 0o755); err != nil {
return fmt.Errorf("could not create datasets directory %s: %v", datasetsDir, err)
}
if err := os.MkdirAll(resultsDir, 0o755); err != nil {
return fmt.Errorf("could not create results directory %s: %v", resultsDir, err)
}
// Create and start the container.
respContainer, err := cli.ContainerCreate(ctx, &container.Config{
Image: dockerImageName,
Tty: true,
AttachStdout: true,
AttachStderr: true,
}, &container.HostConfig{
Mounts: []mount.Mount{
{
Type: mount.TypeBind,
Source: path.Join(algorithm.AlgoWorkingDir, algorithm.DatasetsDir),
Target: datasetsMountPath,
},
{
Type: mount.TypeBind,
Source: path.Join(algorithm.AlgoWorkingDir, algorithm.ResultsDir),
Target: resultsMountPath,
},
},
}, nil, nil, fmt.Sprintf("%s-%s", containerName, d.cmpID))
if err != nil {
return fmt.Errorf("could not create a Docker container: %v", err)
}
if err := cli.ContainerStart(ctx, respContainer.ID, container.StartOptions{}); err != nil {
return fmt.Errorf("could not start a Docker container: %v", err)
}
stdout, err := cli.ContainerLogs(ctx, respContainer.ID, container.LogsOptions{ShowStdout: true, Follow: true})
if err != nil {
return fmt.Errorf("could not read stdout from the container: %v", err)
}
defer stdout.Close()
go func() {
if err := writeToOut(stdout, d.stdout); err != nil {
d.logger.Warn(fmt.Sprintf("could not write to stdout: %v", err))
}
}()
stderr, err := cli.ContainerLogs(ctx, respContainer.ID, container.LogsOptions{ShowStderr: true, Follow: true})
if err != nil {
d.logger.Warn(fmt.Sprintf("could not read stderr from the container: %v", err))
}
defer stderr.Close()
go func() {
if err := writeToOut(stderr, d.stderr); err != nil {
d.logger.Warn(fmt.Sprintf("could not write to stderr: %v", err))
}
}()
statusCh, errCh := cli.ContainerWait(ctx, respContainer.ID, container.WaitConditionNotRunning)
select {
case err := <-errCh:
if err != nil {
return fmt.Errorf("could not wait for a Docker container: %v", err)
}
case <-statusCh:
}
defer func() {
if err = cli.ContainerRemove(ctx, respContainer.ID, container.RemoveOptions{Force: true}); err != nil {
d.logger.Warn(fmt.Sprintf("error could not remove container: %v", err))
}
if _, err := cli.ImageRemove(ctx, imageID, image.RemoveOptions{Force: true}); err != nil {
d.logger.Warn(fmt.Sprintf("error could not remove image: %v", err))
}
}()
return nil
}
func writeToOut(readCloser io.ReadCloser, ioWriter io.Writer) error {
scanner := bufio.NewScanner(readCloser)
for scanner.Scan() {
if _, err := ioWriter.Write(scanner.Bytes()); err != nil {
return fmt.Errorf("error writing to output: %v", err)
}
}
if err := scanner.Err(); err != nil {
return fmt.Errorf("error reading container logs error: %v", err)
}
return nil
}
func (d *docker) Stop() error {
// To be supported later.
return nil
}