*
* Copyright (c) 2025 Bocloud Technologies Co., Ltd.
* installer is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* <http://license.coscl.org.cn/MulanPSL2>
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*
*/
package server
import (
"context"
"fmt"
"testing"
"time"
"github.com/agiledragon/gomonkey/v2"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
"github.com/stretchr/testify/assert"
"gopkg.openfuyao.cn/bkeadm/pkg/executor/containerd"
"gopkg.openfuyao.cn/bkeadm/pkg/executor/docker"
"gopkg.openfuyao.cn/bkeadm/pkg/global"
"gopkg.openfuyao.cn/bkeadm/pkg/infrastructure"
"gopkg.openfuyao.cn/bkeadm/utils"
"gopkg.openfuyao.cn/bkeadm/utils/log"
)
func TestStartChartRegistry(t *testing.T) {
tests := []struct {
name string
containerName string
image string
port string
dataDir string
mockIsContainerd func() bool
mockIsDocker func() bool
mockStartWithContainerd func(string, string, string, string) error
mockStartWithDocker func(string, string, string, string) error
expectError bool
}{
{
name: "start with containerd when only containerd is available",
containerName: "test-chart-registry",
image: "chartmuseum:latest",
port: "8080",
dataDir: "/var/lib/charts",
mockIsContainerd: func() bool { return true },
mockIsDocker: func() bool { return false },
mockStartWithContainerd: func(name, image, port, dataDir string) error { return nil },
mockStartWithDocker: func(name, image, port, dataDir string) error {
t.Error("startWithDocker should not be called when only containerd is available")
return nil
},
expectError: false,
},
{
name: "start with docker when docker is available",
containerName: "test-chart-registry",
image: "chartmuseum:latest",
port: "8080",
dataDir: "/var/lib/charts",
mockIsContainerd: func() bool { return false },
mockIsDocker: func() bool { return true },
mockStartWithDocker: func(name, image, port, dataDir string) error { return nil },
mockStartWithContainerd: func(name, image, port, dataDir string) error {
t.Error("startWithContainerd should not be called when docker is available")
return nil
},
expectError: false,
},
{
name: "both runtimes available, docker takes precedence",
containerName: "test-chart-registry",
image: "chartmuseum:latest",
port: "8080",
dataDir: "/var/lib/charts",
mockIsContainerd: func() bool { return true },
mockIsDocker: func() bool { return true },
mockStartWithDocker: func(name, image, port, dataDir string) error { return nil },
mockStartWithContainerd: func(name, image, port, dataDir string) error {
t.Error("startWithContainerd should not be called when docker is available")
return nil
},
expectError: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc(infrastructure.IsContainerd, tt.mockIsContainerd)
patches.ApplyFunc(infrastructure.IsDocker, tt.mockIsDocker)
if tt.mockStartWithContainerd != nil {
patches.ApplyFunc(startChartRegistryWithContainerd, tt.mockStartWithContainerd)
}
if tt.mockStartWithDocker != nil {
patches.ApplyFunc(startChartRegistryWithDocker, tt.mockStartWithDocker)
}
err := StartChartRegistry(tt.containerName, tt.image, tt.port, tt.dataDir)
if tt.expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
func TestStartChartRegistryWithDocker(t *testing.T) {
tests := []struct {
name string
mockEnsureDockerImageAndContainer func(string, string, string) (bool, error)
mockRunDockerChartRegistry func(string, string, string, string) error
mockWaitForDockerContainerRunning func(string, string)
expectError bool
}{
{
name: "successful registry start",
mockEnsureDockerImageAndContainer: func(name, image, service string) (bool, error) {
return false, nil
},
mockRunDockerChartRegistry: func(name, image, port, dataDir string) error {
return nil
},
mockWaitForDockerContainerRunning: func(name, serviceType string) {
assert.Equal(t, "test-chart-registry", name)
assert.Equal(t, "chart", serviceType)
},
expectError: false,
},
{
name: "ensure docker image and container fails",
mockEnsureDockerImageAndContainer: func(name, image, service string) (bool, error) {
return false, fmt.Errorf("ensure failed")
},
expectError: true,
},
{
name: "container already running",
mockEnsureDockerImageAndContainer: func(name, image, service string) (bool, error) {
return true, nil
},
expectError: false,
},
{
name: "run docker chart registry fails",
mockEnsureDockerImageAndContainer: func(name, image, service string) (bool, error) {
return false, nil
},
mockRunDockerChartRegistry: func(name, image, port, dataDir string) error {
return fmt.Errorf("run failed")
},
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc(ensureDockerImageAndContainer, tt.mockEnsureDockerImageAndContainer)
if tt.mockRunDockerChartRegistry != nil {
patches.ApplyFunc(runDockerChartRegistry, tt.mockRunDockerChartRegistry)
}
if tt.mockWaitForDockerContainerRunning != nil {
patches.ApplyFunc(waitForDockerContainerRunning, tt.mockWaitForDockerContainerRunning)
}
err := startChartRegistryWithDocker("test-chart-registry", "chartmuseum:latest", "8080", "/var/lib/charts")
if tt.expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
func TestWaitForDockerContainerRunning(t *testing.T) {
sleepPatches := gomonkey.ApplyFunc(time.Sleep, func(time.Duration) {})
defer sleepPatches.Reset()
mockDockerClient := &docker.Client{}
originalDocker := global.Docker
global.Docker = mockDockerClient
defer func() {
global.Docker = originalDocker
}()
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc((*docker.Client).GetClient, func(client *docker.Client) *client.Client {
return client.Client
})
patches.ApplyFunc((*client.Client).ContainerInspect, func(client *client.Client, ctx context.Context, containerID string) (container.InspectResponse, error) {
return container.InspectResponse{ContainerJSONBase: &types.ContainerJSONBase{State: &types.ContainerState{Running: true}}}, nil
})
waitForDockerContainerRunning("test-container", "chart")
assert.True(t, true)
}
func TestStartChartRegistryWithContainerd(t *testing.T) {
sleepPatches := gomonkey.ApplyFunc(time.Sleep, func(time.Duration) {})
defer sleepPatches.Reset()
tests := []struct {
name string
mockEnsureImageExists func(string) error
mockEnsureContainerRun func(string) (bool, error)
mockRun func([]string) error
mockContainerInspect func(string) (containerd.NerdContainerInfo, error)
expectError bool
}{
{
name: "successful registry start with containerd",
mockEnsureImageExists: func(image string) error {
return nil
},
mockEnsureContainerRun: func(name string) (bool, error) {
return false, nil
},
mockRun: func(args []string) error {
return nil
},
mockContainerInspect: func(name string) (containerd.NerdContainerInfo, error) {
info := containerd.NerdContainerInfo{}
info.State.Status = "running"
info.State.Running = true
return info, nil
},
expectError: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc(containerd.EnsureImageExists, tt.mockEnsureImageExists)
patches.ApplyFunc(containerd.EnsureContainerRun, tt.mockEnsureContainerRun)
if tt.mockRun != nil {
patches.ApplyFunc(containerd.Run, tt.mockRun)
}
if tt.mockContainerInspect != nil {
patches.ApplyFunc(containerd.ContainerInspect, tt.mockContainerInspect)
}
err := startChartRegistryWithContainerd("test-chart-registry", "chartmuseum:latest", "8080", "/var/lib/charts")
if tt.expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
func TestRemoveChartRegistry(t *testing.T) {
tests := []struct {
name string
containerName string
mockRemoveContainerWithRetry func(string, func()) error
expectError bool
}{
{
name: "successful removal",
containerName: "test-chart-registry",
mockRemoveContainerWithRetry: func(name string, extraCleanup func()) error {
assert.Equal(t, "test-chart-registry", name)
assert.Nil(t, extraCleanup)
return nil
},
expectError: false,
},
{
name: "removal fails",
containerName: "test-chart-registry",
mockRemoveContainerWithRetry: func(name string, extraCleanup func()) error {
return fmt.Errorf("removal failed")
},
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc(removeContainerWithRetry, tt.mockRemoveContainerWithRetry)
patches.ApplyFunc(log.SteppedInfo, func(stepName string, args ...any) {})
err := RemoveChartRegistry(tt.containerName)
if tt.expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
func TestStartChartRegistryWithRealDockerClient(t *testing.T) {
mockDockerClient := &docker.Client{}
originalDocker := global.Docker
global.Docker = mockDockerClient
defer func() {
global.Docker = originalDocker
}()
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc(infrastructure.IsContainerd, func() bool { return false })
patches.ApplyFunc(infrastructure.IsDocker, func() bool { return true })
patches.ApplyFunc((*docker.Client).EnsureImageExists, func(c *docker.Client, ref docker.ImageRef, opts utils.RetryOptions) error {
return nil
})
patches.ApplyFunc((*docker.Client).EnsureContainerRun, func(c *docker.Client, name string) (bool, error) {
return false, nil
})
patches.ApplyFunc(runDockerChartRegistry, func(name, image, port, dataDir string) error {
return nil
})
patches.ApplyFunc(waitForDockerContainerRunning, func(name, serviceType string) {})
err := StartChartRegistry("test-chart-registry", "chartmuseum:latest", "8080", "/var/lib/charts")
assert.NoError(t, err)
}