Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package snapshot
import (
"context"
"errors"
"os"
"os/exec"
"path/filepath"
"testing"
"github.com/agiledragon/gomonkey/v2"
"github.com/stretchr/testify/assert"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"ascend-common/common-utils/hwlog"
"ascend-common/common-utils/utils"
"nodeD/pkg/common"
"nodeD/pkg/kubeclient"
)
func init() {
hwLogConfig := hwlog.LogConfig{
OnlyToStdout: true,
}
hwlog.InitRunLogger(&hwLogConfig, context.Background())
}
func TestNewPodMonitor(t *testing.T) {
client := &kubeclient.ClientK8s{}
monitor := NewPodMonitor(client)
assert.NotNil(t, monitor)
assert.Equal(t, client, monitor.client)
assert.NotNil(t, monitor.stopChan)
}
func TestPodMonitor_Stop(t *testing.T) {
monitor := &PodMonitor{
stopChan: make(chan struct{}, 1),
}
monitor.Stop()
select {
case <-monitor.stopChan:
default:
t.Error("Stop method did not send signal to stopChan")
}
}
func TestPodMonitor_AddPod(t *testing.T) {
monitor := &PodMonitor{}
monitor.AddPod(nil)
monitor.AddPod("not a pod")
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod-0",
Namespace: "default",
Annotations: map[string]string{
common.SnapshotFinishFlag: common.Finished,
},
},
}
patches := gomonkey.ApplyPrivateMethod(&PodMonitor{}, "containerSnapshot", func(pod *v1.Pod, podKey string) {
return
})
defer patches.Reset()
monitor.AddPod(pod)
}
func TestPodMonitor_UpdatePod(t *testing.T) {
monitor := &PodMonitor{}
monitor.UpdatePod(nil, nil)
monitor.UpdatePod(nil, "not a pod")
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod-0",
Namespace: "default",
},
}
patches := gomonkey.ApplyPrivateMethod(&PodMonitor{}, "containerSnapshot", func(pod *v1.Pod, podKey string) {
return
})
defer patches.Reset()
monitor.UpdatePod(nil, pod)
}
func TestPodMonitor_containerSnapshot(t *testing.T) {
monitor := &PodMonitor{}
podWithAnnotation := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod-0",
Namespace: "default",
Annotations: map[string]string{
common.SnapshotFinishFlag: common.Finished,
common.SnapshotMode: "save",
},
},
}
monitor.containerSnapshot(podWithAnnotation, "default/test-pod-0")
podNotReady := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod-0",
Namespace: "default",
Annotations: map[string]string{
common.SnapshotMode: "save",
},
},
Status: v1.PodStatus{
Conditions: []v1.PodCondition{
{
Type: v1.PodReady,
Status: v1.ConditionFalse,
},
},
},
}
monitor.containerSnapshot(podNotReady, "default/test-pod-0")
podWithMultipleContainers := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod-0",
Namespace: "default",
Annotations: map[string]string{
common.SnapshotMode: "save",
},
},
Status: v1.PodStatus{
Conditions: []v1.PodCondition{
{
Type: v1.PodReady,
Status: v1.ConditionTrue,
},
},
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{Name: "container1"},
{Name: "container2"},
},
},
}
monitor.containerSnapshot(podWithMultipleContainers, "default/test-pod-0")
podWithoutSnapshotPath := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod-0",
Namespace: "default",
Annotations: map[string]string{
common.SnapshotMode: "save",
},
},
Status: v1.PodStatus{
Conditions: []v1.PodCondition{
{
Type: v1.PodReady,
Status: v1.ConditionTrue,
},
},
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "container1",
Env: []v1.EnvVar{},
},
},
},
}
monitor.containerSnapshot(podWithoutSnapshotPath, "default/test-pod-0")
normalPod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod-0",
Namespace: "default",
Annotations: map[string]string{
common.SnapshotMode: "save",
},
},
Status: v1.PodStatus{
Conditions: []v1.PodCondition{
{
Type: v1.PodReady,
Status: v1.ConditionTrue,
},
},
ContainerStatuses: []v1.ContainerStatus{
{
ContainerID: "container-id",
},
},
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "container1",
Env: []v1.EnvVar{
{
Name: common.SnapshotPath,
Value: "/snapshot",
},
},
},
},
},
}
patches := gomonkey.NewPatches()
patches.ApplyFunc(utils.GetLastNumberFromString, func(s string) (string, error) {
return "0", nil
})
patches.ApplyPrivateMethod(&PodMonitor{}, "checkpoint", func(pod *v1.Pod, podKey string, containerId string, snapshotPath string) {
return
})
defer patches.Reset()
monitor.containerSnapshot(normalPod, "default/test-pod-0")
}
func TestPodMonitor_checkpoint(t *testing.T) {
client := &kubeclient.ClientK8s{}
monitor := &PodMonitor{
client: client,
}
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod-0",
Namespace: "default",
},
}
patches := gomonkey.ApplyFunc(doCheckpoint, func(string, string, int) error {
return nil
})
patches.ApplyMethod(&kubeclient.ClientK8s{}, "UpdatePodAnnotation", func(*kubeclient.ClientK8s, string, string, *v1.Pod) error {
return nil
})
defer patches.Reset()
monitor.checkpoint(pod, "default/test-pod-0", "container-id", "/snapshot/0")
patches = gomonkey.ApplyFunc(doCheckpoint, func(string, string, int) error {
return errors.New("checkpoint failed")
})
defer patches.Reset()
monitor.checkpoint(pod, "default/test-pod-0", "container-id", "/snapshot/0")
patches = gomonkey.ApplyFunc(doCheckpoint, func(string, string, int) error {
return nil
})
patches.ApplyMethod(&kubeclient.ClientK8s{}, "UpdatePodAnnotation", func(*kubeclient.ClientK8s, string, string, *v1.Pod) error {
return errors.New("update annotation failed")
})
defer patches.Reset()
monitor.checkpoint(pod, "default/test-pod-0", "container-id", "/snapshot/0")
}
func TestDoCheckpoint(t *testing.T) {
patches := gomonkey.NewPatches()
defer patches.Reset()
tempDir, err := os.MkdirTemp("", "test-snapshot")
assert.NoError(t, err)
defer os.RemoveAll(tempDir)
err = doCheckpoint("container-id", tempDir, -1)
assert.Error(t, err)
assert.Contains(t, err.Error(), "already exists")
patches.ApplyFunc(exec.LookPath, func(string) (string, error) {
return "", errors.New("not found")
})
err = doCheckpoint("container-id", filepath.Join(tempDir, "new"), -1)
assert.Error(t, err)
assert.Contains(t, err.Error(), "ascend-docker-runtime not found")
patches = gomonkey.ApplyFunc(exec.LookPath, func(string) (string, error) {
return "/bin/echo", nil
})
patches.ApplyFunc(RunCmd, func(string, []string, []string, int) (int, error) {
return 1, errors.New("run cmd failed")
})
defer patches.Reset()
err = doCheckpoint("container-id", filepath.Join(tempDir, "new"), -1)
assert.Error(t, err)
assert.Contains(t, err.Error(), "run cmd failed")
patches = gomonkey.ApplyFunc(RunCmd, func(string, []string, []string, int) (int, error) {
return 1, nil
})
defer patches.Reset()
err = doCheckpoint("container-id", filepath.Join(tempDir, "new"), -1)
assert.Error(t, err)
assert.Contains(t, err.Error(), "checkpoint failed with exit code")
patches = gomonkey.ApplyFunc(RunCmd, func(string, []string, []string, int) (int, error) {
return 0, nil
})
defer patches.Reset()
err = doCheckpoint("container-id", filepath.Join(tempDir, "new"), -1)
assert.NoError(t, err)
patches = gomonkey.ApplyFunc(RunCmd, func(string, []string, []string, int) (int, error) {
return 0, ErrCommandTimeout
})
patches.ApplyFunc(tryResumeContainer, func(string, string) error {
return nil
})
defer patches.Reset()
err = doCheckpoint("container-id", filepath.Join(tempDir, "new"), 1)
assert.Error(t, err)
assert.Contains(t, err.Error(), "checkpoint timeout")
patches = gomonkey.ApplyFunc(tryResumeContainer, func(string, string) error {
return errors.New("resume failed")
})
defer patches.Reset()
err = doCheckpoint("container-id", filepath.Join(tempDir, "new"), 1)
assert.Error(t, err)
assert.Contains(t, err.Error(), "failed to resume container after process termination")
}
func TestTryResumeContainer(t *testing.T) {
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc(exec.LookPath, func(string) (string, error) {
return "", errors.New("not found")
})
err := tryResumeContainer("container-id", "/snapshot")
assert.Error(t, err)
assert.Contains(t, err.Error(), "ascend-docker-runtime not found for resume")
patches = gomonkey.ApplyFunc(exec.LookPath, func(string) (string, error) {
return "/bin/echo", nil
})
patches.ApplyFunc((*exec.Cmd).CombinedOutput, func(*exec.Cmd) ([]byte, error) {
return []byte("success"), nil
})
defer patches.Reset()
err = tryResumeContainer("container-id", "/snapshot")
assert.NoError(t, err)
patches = gomonkey.ApplyFunc((*exec.Cmd).CombinedOutput, func(*exec.Cmd) ([]byte, error) {
return []byte("container not paused"), errors.New("error")
})
defer patches.Reset()
err = tryResumeContainer("container-id", "/snapshot")
assert.NoError(t, err)
patches = gomonkey.ApplyFunc((*exec.Cmd).CombinedOutput, func(*exec.Cmd) ([]byte, error) {
return []byte("failed"), errors.New("error")
})
defer patches.Reset()
err = tryResumeContainer("container-id", "/snapshot")
assert.Error(t, err)
assert.Contains(t, err.Error(), "runc resume failed")
}