Skip to content
Snippets Groups Projects
Commit 8f34615a authored by Denis O's avatar Denis O Committed by Artyom Kartasov
Browse files

fix: add logic to detect and start existing UI container (#371)

parent 2cc3dcb0
No related branches found
No related tags found
1 merge request!538fix: add logic to detect and start existing UI container (#371)
......@@ -91,53 +91,66 @@ func (ui *UIManager) isConfigChanged(cfg Config) bool {
// Run creates a new embedded UI container.
func (ui *UIManager) Run(ctx context.Context) error {
if err := docker.PrepareImage(ui.runner, ui.cfg.DockerImage); err != nil {
if err := docker.PrepareImage(ctx, ui.docker, ui.cfg.DockerImage); err != nil {
return fmt.Errorf("failed to prepare Docker image: %w", err)
}
embeddedUI, err := ui.docker.ContainerCreate(ctx,
&container.Config{
Labels: map[string]string{
cont.DBLabSatelliteLabel: cont.DBLabEmbeddedUILabel,
cont.DBLabInstanceIDLabel: ui.engProps.InstanceID,
cont.DBLabEngineNameLabel: ui.engProps.ContainerName,
},
Image: ui.cfg.DockerImage,
Env: []string{
EnvEngineName + "=" + ui.engProps.ContainerName,
EnvEnginePort + "=" + strconv.FormatUint(uint64(ui.engProps.EnginePort), 10),
},
Healthcheck: &container.HealthConfig{
Interval: healthCheckInterval,
Timeout: healthCheckTimeout,
Retries: healthCheckRetries,
var containerID = ""
// try to fetch an existing UI container
containerData, err := ui.docker.ContainerInspect(ctx, getEmbeddedUIName(ui.engProps.InstanceID))
if err == nil {
containerID = containerData.ID
}
if containerID == "" {
embeddedUI, err := ui.docker.ContainerCreate(ctx,
&container.Config{
Labels: map[string]string{
cont.DBLabSatelliteLabel: cont.DBLabEmbeddedUILabel,
cont.DBLabInstanceIDLabel: ui.engProps.InstanceID,
cont.DBLabEngineNameLabel: ui.engProps.ContainerName,
},
Image: ui.cfg.DockerImage,
Env: []string{
EnvEngineName + "=" + ui.engProps.ContainerName,
EnvEnginePort + "=" + strconv.FormatUint(uint64(ui.engProps.EnginePort), 10),
},
Healthcheck: &container.HealthConfig{
Interval: healthCheckInterval,
Timeout: healthCheckTimeout,
Retries: healthCheckRetries,
},
},
},
&container.HostConfig{
PortBindings: map[nat.Port][]nat.PortBinding{
"80/tcp": {
{
HostIP: ui.cfg.Host,
HostPort: strconv.Itoa(ui.cfg.Port),
&container.HostConfig{
PortBindings: map[nat.Port][]nat.PortBinding{
"80/tcp": {
{
HostIP: ui.cfg.Host,
HostPort: strconv.Itoa(ui.cfg.Port),
},
},
},
},
},
&network.NetworkingConfig{},
nil,
getEmbeddedUIName(ui.engProps.InstanceID),
)
if err != nil {
return fmt.Errorf("failed to prepare Docker image for embedded UI: %w", err)
&network.NetworkingConfig{},
nil,
getEmbeddedUIName(ui.engProps.InstanceID),
)
if err != nil {
return fmt.Errorf("failed to prepare Docker image for embedded UI: %w", err)
}
containerID = embeddedUI.ID
}
if err := networks.Connect(ctx, ui.docker, ui.engProps.InstanceID, embeddedUI.ID); err != nil {
if err := networks.Connect(ctx, ui.docker, ui.engProps.InstanceID, containerID); err != nil {
return fmt.Errorf("failed to connect UI container to the internal Docker network: %w", err)
}
if err := ui.docker.ContainerStart(ctx, embeddedUI.ID, types.ContainerStartOptions{}); err != nil {
return fmt.Errorf("failed to start container %q: %w", embeddedUI.ID, err)
if err := ui.docker.ContainerStart(ctx, containerID, types.ContainerStartOptions{}); err != nil {
return fmt.Errorf("failed to start container %q: %w", containerID, err)
}
reportLaunching(ui.cfg)
......
//go:build integration
// +build integration
/*
2021 © Postgres.ai
*/
package embeddedui
import (
"context"
"testing"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gitlab.com/postgres-ai/database-lab/v3/internal/provision/runners"
"gitlab.com/postgres-ai/database-lab/v3/internal/retrieval/engine/postgres/tools"
"gitlab.com/postgres-ai/database-lab/v3/pkg/config/global"
"gitlab.com/postgres-ai/database-lab/v3/pkg/util/networks"
)
func TestStartExistingContainer(t *testing.T) {
t.Parallel()
docker, err := client.NewClientWithOpts(client.FromEnv)
require.NoError(t, err)
engProps := global.EngineProps{
InstanceID: "testuistart",
}
embeddedUI := New(
Config{
// "mock" UI image
DockerImage: "gcr.io/google_containers/pause-amd64:3.0",
},
engProps,
runners.NewLocalRunner(false),
docker,
)
ctx := context.TODO()
networks.Setup(ctx, docker, engProps.InstanceID, getEmbeddedUIName(engProps.InstanceID))
// clean test UI container
defer tools.RemoveContainer(ctx, docker, getEmbeddedUIName(engProps.InstanceID), 30*time.Second)
// start UI container
err = embeddedUI.Run(ctx)
require.NoError(t, err)
// explicitly stop container
tools.StopContainer(ctx, docker, getEmbeddedUIName(engProps.InstanceID), 30*time.Second)
// start UI container back
err = embeddedUI.Run(ctx)
require.NoError(t, err)
// list containers
filterArgs := filters.NewArgs()
filterArgs.Add("name", getEmbeddedUIName(engProps.InstanceID))
list, err := docker.ContainerList(
ctx,
types.ContainerListOptions{
All: true,
Filters: filterArgs,
},
)
require.NoError(t, err)
assert.NotEmpty(t, list)
// initial container
assert.Equal(t, "running", list[0].State)
}
......@@ -9,12 +9,14 @@ import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"path"
"strconv"
"strings"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client"
"github.com/pkg/errors"
"github.com/shirou/gopsutil/host"
......@@ -22,14 +24,24 @@ import (
"gitlab.com/postgres-ai/database-lab/v3/internal/provision/resources"
"gitlab.com/postgres-ai/database-lab/v3/internal/provision/runners"
"gitlab.com/postgres-ai/database-lab/v3/internal/retrieval/engine/postgres/tools"
"gitlab.com/postgres-ai/database-lab/v3/pkg/log"
)
const (
labelClone = "dblab_clone"
// referenceKey uses as a filtering key to identify image tag.
referenceKey = "reference"
)
var systemVolumes = []string{"/sys", "/lib", "/proc"}
// imagePullProgress describes the progress of pulling the container image.
type imagePullProgress struct {
Status string `json:"status"`
Progress string `json:"progress"`
}
// RunContainer runs specified container.
func RunContainer(r runners.Runner, c *resources.AppConfig) error {
hostInfo, err := host.Info()
......@@ -221,8 +233,8 @@ func Exec(r runners.Runner, c *resources.AppConfig, cmd string) (string, error)
}
// PrepareImage prepares a Docker image to use.
func PrepareImage(runner runners.Runner, dockerImage string) error {
imageExists, err := ImageExists(runner, dockerImage)
func PrepareImage(ctx context.Context, docker *client.Client, dockerImage string) error {
imageExists, err := ImageExists(ctx, docker, dockerImage)
if err != nil {
return fmt.Errorf("cannot check docker image existence: %w", err)
}
......@@ -231,7 +243,7 @@ func PrepareImage(runner runners.Runner, dockerImage string) error {
return nil
}
if err := PullImage(runner, dockerImage); err != nil {
if err := PullImage(ctx, docker, dockerImage); err != nil {
return fmt.Errorf("cannot pull docker image: %w", err)
}
......@@ -239,23 +251,50 @@ func PrepareImage(runner runners.Runner, dockerImage string) error {
}
// ImageExists checks existence of Docker image.
func ImageExists(r runners.Runner, dockerImage string) (bool, error) {
dockerListImagesCmd := "docker images " + dockerImage + " --quiet"
func ImageExists(ctx context.Context, docker *client.Client, dockerImage string) (bool, error) {
filterArgs := filters.NewArgs()
filterArgs.Add(referenceKey, dockerImage)
list, err := docker.ImageList(ctx, types.ImageListOptions{
All: false,
Filters: filterArgs,
})
out, err := r.Run(dockerListImagesCmd, true)
if err != nil {
return false, fmt.Errorf("failed to list images: %w", err)
}
return len(strings.TrimSpace(out)) > 0, nil
return len(list) > 0, nil
}
// PullImage pulls Docker image from DockerHub registry.
func PullImage(r runners.Runner, dockerImage string) error {
dockerPullImageCmd := "docker pull " + dockerImage
func PullImage(ctx context.Context, docker *client.Client, dockerImage string) error {
pullResponse, err := docker.ImagePull(ctx, dockerImage, types.ImagePullOptions{})
if err != nil {
return fmt.Errorf("failed to pull image: %w", err)
}
// reading output of image pulling, without reading pull will not be performed
decoder := json.NewDecoder(pullResponse)
if _, err := r.Run(dockerPullImageCmd, true); err != nil {
return fmt.Errorf("failed to pull images: %w", err)
for {
var pullResult imagePullProgress
if err := decoder.Decode(&pullResult); err != nil {
if errors.Is(err, io.EOF) {
break
}
return fmt.Errorf("failed to pull image: %w", err)
}
log.Dbg("Image pulling progress", pullResult.Status, pullResult.Progress)
}
err = pullResponse.Close()
if err != nil {
return fmt.Errorf("failed to pull image: %w", err)
}
return nil
......
......@@ -125,7 +125,7 @@ func (p *Provisioner) Init() error {
return fmt.Errorf("failed to revise port pool: %w", err)
}
if err := docker.PrepareImage(p.runner, p.config.DockerImage); err != nil {
if err := docker.PrepareImage(p.ctx, p.dockerClient, p.config.DockerImage); err != nil {
return fmt.Errorf("cannot prepare docker image %s: %w", p.config.DockerImage, err)
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment