mirror of
https://github.com/ultravioletrs/cocos.git
synced 2026-08-07 07:14:50 +00:00
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
* 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>
195 lines
5.3 KiB
Go
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
|
|
}
|