*
* 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 (
"testing"
"time"
"github.com/agiledragon/gomonkey/v2"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
specs "github.com/opencontainers/image-spec/specs-go/v1"
"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"
)
const (
testNFSName = "test-nfs"
testNFSImage = "itsthenetwork/nfs-ganesha:latest"
testNFSDataDir = "/var/nfs"
testNFSPort = "2049"
testContainerCount = 2
)
func TestStartNFSServerWithDocker(t *testing.T) {
tests := []struct {
name string
mockIsDocker func() bool
mockIsContainerd func() bool
mockEnsureImageAndContainer func(name, image, serviceName string) (bool, error)
mockRunDockerNFSServer func(string, string, string) error
expectError bool
}{
{
name: "successful start with docker",
mockIsDocker: func() bool {
return true
},
mockIsContainerd: func() bool {
return false
},
mockEnsureImageAndContainer: func(name, image, serviceName string) (bool, error) {
return false, nil
},
mockRunDockerNFSServer: func(name, image, nfsDataDirectory string) error {
return nil
},
expectError: false,
},
{
name: "nfs already running",
mockIsDocker: func() bool {
return true
},
mockIsContainerd: func() bool {
return false
},
mockEnsureImageAndContainer: func(name, image, serviceName string) (bool, error) {
return true, nil
},
expectError: false,
},
{
name: "ensure image and container fails",
mockIsDocker: func() bool {
return true
},
mockIsContainerd: func() bool {
return false
},
mockEnsureImageAndContainer: func(name, image, serviceName string) (bool, error) {
return false, assert.AnError
},
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc(infrastructure.IsDocker, tt.mockIsDocker)
patches.ApplyFunc(infrastructure.IsContainerd, tt.mockIsContainerd)
if tt.mockEnsureImageAndContainer != nil {
patches.ApplyFunc(ensureDockerImageAndContainer, tt.mockEnsureImageAndContainer)
}
if tt.mockRunDockerNFSServer != nil {
patches.ApplyFunc(runDockerNFSServer, tt.mockRunDockerNFSServer)
}
patches.ApplyFunc(waitForDockerContainerRunning, func(name, serviceType string) {})
err := StartNFSServer(testNFSName, testNFSImage, testNFSDataDir)
if tt.expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
func TestStartNFSServerWithContainerd(t *testing.T) {
sleepPatches := gomonkey.ApplyFunc(time.Sleep, func(time.Duration) {})
defer sleepPatches.Reset()
tests := []struct {
name string
mockIsDocker func() bool
mockIsContainerd func() bool
mockContainerdEnsureImageExists func(string) error
mockContainerdEnsureContainerRun func(string) (bool, error)
mockContainerdRun func([]string) error
mockContainerdInspect func(string) (containerd.NerdContainerInfo, error)
expectError bool
}{
{
name: "successful start with containerd",
mockIsDocker: func() bool {
return false
},
mockIsContainerd: func() bool {
return true
},
mockContainerdEnsureImageExists: func(image string) error {
return nil
},
mockContainerdEnsureContainerRun: func(name string) (bool, error) {
return false, nil
},
mockContainerdRun: func(script []string) error {
return nil
},
mockContainerdInspect: func(name string) (containerd.NerdContainerInfo, error) {
return containerd.NerdContainerInfo{
State: struct {
Status string `json:"Status"`
Running bool `json:"Running"`
Paused bool `json:"Paused"`
Restarting bool `json:"Restarting"`
Pid uint `json:"Pid"`
ExitCode uint `json:"ExitCode"`
FinishedAt string `json:"FinishedAt"`
}{
Running: true,
},
}, nil
},
expectError: false,
},
{
name: "containerd container already running",
mockIsDocker: func() bool {
return false
},
mockIsContainerd: func() bool {
return true
},
mockContainerdEnsureImageExists: func(image string) error {
return nil
},
mockContainerdEnsureContainerRun: func(name string) (bool, error) {
return true, nil
},
expectError: false,
},
{
name: "containerd ensure image exists fails",
mockIsDocker: func() bool {
return false
},
mockIsContainerd: func() bool {
return true
},
mockContainerdEnsureImageExists: func(image string) error {
return assert.AnError
},
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc(infrastructure.IsDocker, tt.mockIsDocker)
patches.ApplyFunc(infrastructure.IsContainerd, tt.mockIsContainerd)
if tt.mockContainerdEnsureImageExists != nil {
patches.ApplyFunc(containerd.EnsureImageExists, tt.mockContainerdEnsureImageExists)
}
if tt.mockContainerdEnsureContainerRun != nil {
patches.ApplyFunc(containerd.EnsureContainerRun, tt.mockContainerdEnsureContainerRun)
}
if tt.mockContainerdRun != nil {
patches.ApplyFunc(containerd.Run, tt.mockContainerdRun)
}
if tt.mockContainerdInspect != nil {
patches.ApplyFunc(containerd.ContainerInspect, tt.mockContainerdInspect)
}
err := StartNFSServer(testNFSName, testNFSImage, testNFSDataDir)
if tt.expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
func TestStartNFSServerNoRuntime(t *testing.T) {
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc(infrastructure.IsDocker, func() bool {
return false
})
patches.ApplyFunc(infrastructure.IsContainerd, func() bool {
return false
})
err := StartNFSServer(testNFSName, testNFSImage, testNFSDataDir)
assert.NoError(t, err)
}
func TestRemoveNFSServer(t *testing.T) {
tests := []struct {
name string
mockIsDocker func() bool
mockIsContainerd func() bool
mockRemoveContainer func(*docker.Client, string) error
mockContainerExists func(*docker.Client, string) (types.ContainerJSON, bool)
mockExecuteCommand func(string, ...string) error
expectError bool
}{
{
name: "successful removal with docker",
mockIsDocker: func() bool {
return true
},
mockIsContainerd: func() bool {
return false
},
mockRemoveContainer: func(c *docker.Client, name string) error {
return nil
},
mockContainerExists: func(c *docker.Client, name string) (types.ContainerJSON, bool) {
return types.ContainerJSON{}, false
},
mockExecuteCommand: func(command string, args ...string) error {
return nil
},
expectError: false,
},
{
name: "successful removal with containerd",
mockIsDocker: func() bool {
return false
},
mockIsContainerd: func() bool {
return true
},
mockRemoveContainer: func(c *docker.Client, name string) error {
return nil
},
mockContainerExists: func(c *docker.Client, name string) (types.ContainerJSON, bool) {
return types.ContainerJSON{}, false
},
mockExecuteCommand: func(command string, args ...string) error {
return nil
},
expectError: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
originalDocker := global.Docker
global.Docker = &docker.Client{}
defer func() {
global.Docker = originalDocker
}()
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc(infrastructure.IsDocker, tt.mockIsDocker)
patches.ApplyFunc(infrastructure.IsContainerd, tt.mockIsContainerd)
if tt.mockRemoveContainer != nil {
patches.ApplyFunc((*docker.Client).ContainerRemove, tt.mockRemoveContainer)
}
if tt.mockContainerExists != nil {
patches.ApplyFunc((*docker.Client).ContainerExists, tt.mockContainerExists)
}
if tt.mockExecuteCommand != nil {
patches.ApplyFunc(global.Command.ExecuteCommand, tt.mockExecuteCommand)
}
err := RemoveNFSServer(testNFSName)
if tt.expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
func TestRunDockerNFSServer(t *testing.T) {
tests := []struct {
name string
mockRun func(*docker.Client, *container.Config, *container.HostConfig, *network.NetworkingConfig, *specs.Platform, string) error
expectError bool
}{
{
name: "successful docker run",
mockRun: func(c *docker.Client, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *specs.Platform, name string) error {
return nil
},
expectError: false,
},
{
name: "docker run fails",
mockRun: func(c *docker.Client, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *specs.Platform, name string) error {
return assert.AnError
},
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
originalDocker := global.Docker
global.Docker = &docker.Client{}
defer func() {
global.Docker = originalDocker
}()
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc((*docker.Client).Run, tt.mockRun)
err := runDockerNFSServer(testNFSName, testNFSImage, testNFSDataDir)
if tt.expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
func TestStartNFSServerWithContainerdNotRunning(t *testing.T) {
sleepPatches := gomonkey.ApplyFunc(time.Sleep, func(time.Duration) {})
defer sleepPatches.Reset()
inspectCallCount := 0
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc(infrastructure.IsDocker, func() bool {
return false
})
patches.ApplyFunc(infrastructure.IsContainerd, func() bool {
return true
})
patches.ApplyFunc(containerd.EnsureImageExists, func(image string) error {
return nil
})
patches.ApplyFunc(containerd.EnsureContainerRun, func(name string) (bool, error) {
return false, nil
})
patches.ApplyFunc(containerd.Run, func(script []string) error {
return nil
})
patches.ApplyFunc(containerd.ContainerInspect, func(name string) (containerd.NerdContainerInfo, error) {
inspectCallCount++
if inspectCallCount < testContainerCount {
return containerd.NerdContainerInfo{
State: struct {
Status string `json:"Status"`
Running bool `json:"Running"`
Paused bool `json:"Paused"`
Restarting bool `json:"Restarting"`
Pid uint `json:"Pid"`
ExitCode uint `json:"ExitCode"`
FinishedAt string `json:"FinishedAt"`
}{
Running: false,
},
}, nil
}
return containerd.NerdContainerInfo{
State: struct {
Status string `json:"Status"`
Running bool `json:"Running"`
Paused bool `json:"Paused"`
Restarting bool `json:"Restarting"`
Pid uint `json:"Pid"`
ExitCode uint `json:"ExitCode"`
FinishedAt string `json:"FinishedAt"`
}{
Running: true,
},
}, nil
})
err := StartNFSServer(testNFSName, testNFSImage, testNFSDataDir)
assert.NoError(t, err)
}