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 server
import (
"context"
"errors"
"fmt"
"os"
"reflect"
"testing"
"time"
"github.com/agiledragon/gomonkey/v2"
"github.com/smartystreets/goconvey/convey"
"google.golang.org/grpc"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/kubelet/pkg/apis/podresources/v1alpha1"
"Ascend-device-plugin/pkg/common"
)
const (
sockMode = 0755
)
func init() {
if _, err := os.Stat(socketPath); err == nil {
return
}
if _, err := os.OpenFile(socketPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, sockMode); err != nil {
fmt.Errorf("err: %v", err)
return
}
if err := os.Chmod(socketPath, os.ModeSocket); err != nil {
fmt.Errorf("err: %v", err)
return
}
}
func TestPodResourceStart1(t *testing.T) {
pr := NewPodResource()
convey.Convey("test start", t, func() {
convey.Convey("VerifyPath failed", func() {
mockVerifyPath := gomonkey.ApplyFunc(common.VerifyPathAndPermission, func(verifyPath string,
waitSecond int) (string, bool) {
return "", false
})
defer mockVerifyPath.Reset()
convey.So(pr.start(), convey.ShouldNotBeNil)
})
convey.Convey("VerifyPath ok", func() {
mockVerifyPath := gomonkey.ApplyFunc(common.VerifyPathAndPermission, func(verifyPath string,
waitSecond int) (string, bool) {
return "", true
})
defer mockVerifyPath.Reset()
err := pr.start()
convey.So(err, convey.ShouldBeNil)
})
})
}
func TestPodResourceStart2(t *testing.T) {
pr := NewPodResource()
convey.Convey("test start", t, func() {
convey.Convey("GetClient failed", func() {
mockGetClient := gomonkey.ApplyFunc(getV1alpha1Client, func(socket string,
connectionTimeout time.Duration, maxMsgSize int) (v1alpha1.PodResourcesListerClient,
*grpc.ClientConn, error) {
return nil, nil, fmt.Errorf("err")
})
defer mockGetClient.Reset()
convey.So(pr.start(), convey.ShouldNotBeNil)
})
convey.Convey("start ok", func() {
mockGetClient := gomonkey.ApplyFunc(getV1alpha1Client, func(socket string,
connectionTimeout time.Duration, maxMsgSize int) (v1alpha1.PodResourcesListerClient,
*grpc.ClientConn, error) {
return nil, nil, nil
})
defer mockGetClient.Reset()
funcStub := gomonkey.ApplyFunc(common.VerifyPathAndPermission,
func(verifyPathAndPermission string, waitSecond int) (string, bool) {
return verifyPathAndPermission, true
})
defer funcStub.Reset()
convey.So(pr.start(), convey.ShouldBeNil)
})
})
}
func TestPodResourceStop(t *testing.T) {
convey.Convey("test start", t, func() {
convey.Convey("close failed", func() {
pr := &PodResource{conn: &grpc.ClientConn{}}
mockClose := gomonkey.ApplyMethod(reflect.TypeOf(new(grpc.ClientConn)), "Close",
func(_ *grpc.ClientConn) error { return fmt.Errorf("err") })
defer mockClose.Reset()
pr.stop()
convey.So(pr.conn, convey.ShouldBeNil)
})
convey.Convey("close ok", func() {
pr := &PodResource{conn: &grpc.ClientConn{}}
mockClose := gomonkey.ApplyMethod(reflect.TypeOf(new(grpc.ClientConn)), "Close",
func(_ *grpc.ClientConn) error { return nil })
defer mockClose.Reset()
pr.stop()
convey.So(pr.conn, convey.ShouldBeNil)
})
})
}
type FakeClient struct{}
func (c *FakeClient) List(ctx context.Context, in *v1alpha1.ListPodResourcesRequest,
opts ...grpc.CallOption) (*v1alpha1.ListPodResourcesResponse, error) {
out := new(v1alpha1.ListPodResourcesResponse)
return out, nil
}
type getContainerResourceTestCase struct {
Name string
containerResource *v1alpha1.ContainerResources
WantDevices []string
WantErr error
}
func buildGetContainerResourceTestCases() []getContainerResourceTestCase {
return []getContainerResourceTestCase{
{
Name: "01-ContainerResources is nil, should return empty resource and error",
containerResource: nil,
WantDevices: nil,
WantErr: errors.New("invalid container resource"),
},
{
Name: "02-device is list is empty, should return empty resource and error",
containerResource: &v1alpha1.ContainerResources{
Devices: []*v1alpha1.ContainerDevices{
nil,
{ResourceName: "notExistResourceName"},
{ResourceName: common.HuaweiUnHealthAscend910, DeviceIds: []string{}},
},
},
WantDevices: nil,
WantErr: errors.New("container device num 0 exceeds the upper limit"),
},
{
Name: "02-get container resource success, should return resource and nil",
containerResource: &v1alpha1.ContainerResources{
Devices: []*v1alpha1.ContainerDevices{
{ResourceName: common.HuaweiUnHealthAscend910, DeviceIds: []string{"0", "1"}},
},
},
WantDevices: []string{"0", "1"},
WantErr: nil,
},
}
}
func TestGetContainerResource(t *testing.T) {
testCases := buildGetContainerResourceTestCases()
for _, tt := range testCases {
t.Run(tt.Name, func(t *testing.T) {
pr := &PodResource{conn: &grpc.ClientConn{}}
_, deviceIds, err := pr.getContainerResource(tt.containerResource)
if !reflect.DeepEqual(deviceIds, tt.WantDevices) {
t.Errorf("getContainerResource() deviceIds = %v, WantDevices = %v", deviceIds, tt.WantDevices)
}
if !reflect.DeepEqual(err, tt.WantErr) {
t.Errorf("getContainerResource() err = %v, wantErr = %v", err, tt.WantErr)
}
})
}
}
type getDeviceFromPodTestCase struct {
Name string
podResources *v1alpha1.PodResources
WantDevices []string
WantErr error
}
func buildGetDeviceFromPodTestCases() []getDeviceFromPodTestCase {
return []getDeviceFromPodTestCase{
{
Name: "01-podResources is nil, should return empty resource and error",
podResources: nil,
WantDevices: nil,
WantErr: errors.New("invalid podReousrces"),
},
{
Name: "02-get device from pod success, should return resource and nil",
podResources: &v1alpha1.PodResources{
Containers: []*v1alpha1.ContainerResources{
{
Devices: []*v1alpha1.ContainerDevices{
{ResourceName: common.HuaweiUnHealthAscend910, DeviceIds: []string{"0", "1"}},
},
},
{
Devices: []*v1alpha1.ContainerDevices{
{ResourceName: common.HuaweiUnHealthAscend910, DeviceIds: []string{"3", "4"}},
},
},
},
},
WantDevices: []string{"0", "1", "3", "4"},
WantErr: nil,
},
}
}
func TestGetDeviceFromPod(t *testing.T) {
testCases := buildGetDeviceFromPodTestCases()
for _, tt := range testCases {
t.Run(tt.Name, func(t *testing.T) {
pr := &PodResource{conn: &grpc.ClientConn{}}
_, deviceIds, err := pr.getDeviceFromPod(tt.podResources)
if !reflect.DeepEqual(deviceIds, tt.WantDevices) {
t.Errorf("getDeviceFromPod() deviceIds = %v, WantDevices = %v", deviceIds, tt.WantDevices)
}
if !reflect.DeepEqual(err, tt.WantErr) {
t.Errorf("getDeviceFromPod() err = %v, wantErr = %v", err, tt.WantErr)
}
})
}
}
type assemblePodResourceTestCase struct {
Name string
resp *v1alpha1.ListPodResourcesResponse
listErr error
WantDevices map[string]PodDevice
WantErr error
}
func buildAssemblePodResourceTestCases() []assemblePodResourceTestCase {
return []assemblePodResourceTestCase{
{
Name: "01-client list error, should return empty pod resource and error",
resp: nil,
listErr: errors.New("fake client list error"),
WantDevices: nil,
WantErr: errors.New("list pod resource failed, err: fake client list error"),
},
{
Name: "02-assemble pod success, should return pod resource and nil",
resp: &v1alpha1.ListPodResourcesResponse{
PodResources: []*v1alpha1.PodResources{
nil,
{Name: "INVALID-NAME", Namespace: "namespace"},
{Name: "valid-name", Namespace: ".invalid"},
{Name: "valid-name", Namespace: "namespace", Containers: []*v1alpha1.ContainerResources{
{
Devices: []*v1alpha1.ContainerDevices{
{ResourceName: common.HuaweiUnHealthAscend910, DeviceIds: []string{"0", "1"}},
},
},
}},
},
},
listErr: nil,
WantDevices: map[string]PodDevice{
"namespace_valid-name": {
ResourceName: common.HuaweiUnHealthAscend910,
DeviceIds: []string{"0", "1"},
},
},
WantErr: nil,
},
}
}
func TestAssemblePodResource(t *testing.T) {
testCases := buildAssemblePodResourceTestCases()
for _, tt := range testCases {
t.Run(tt.Name, func(t *testing.T) {
pr := &PodResource{conn: &grpc.ClientConn{}, client: &FakeClient{}}
patch := gomonkey.ApplyMethodReturn(&FakeClient{}, "List", tt.resp, tt.listErr)
podResourceList, err := pr.assemblePodResource()
patch.Reset()
if !reflect.DeepEqual(podResourceList, tt.WantDevices) {
t.Errorf("assemblePodResource() podResourceList = %v, "+
"WantDevices = %v", podResourceList, tt.WantDevices)
}
if !reflect.DeepEqual(err, tt.WantErr) {
t.Errorf("assemblePodResource() err = %v, wantErr = %v", err, tt.WantErr)
}
})
}
}
func TestStop(t *testing.T) {
pr := NewPodResource()
pr.conn = &grpc.ClientConn{}
pr.client = &FakeClient{}
patch := gomonkey.ApplyMethodReturn(&grpc.ClientConn{}, "Close", errors.New("fake close error"))
defer patch.Reset()
pr.stop()
if pr.conn != nil || pr.client != nil {
t.Errorf("conn: %v and client: %v should be nilisCompleted", pr.conn, pr.client)
}
}
type isPodMoveCompleteTestCase struct {
Name string
podDevices map[string]PodDevice
mockErr error
podList []v1.Pod
wantIsCompleted bool
}
func buildIsPodMoveCompleteTestCases() []isPodMoveCompleteTestCase {
podList := []v1.Pod{
{ObjectMeta: metav1.ObjectMeta{Namespace: "namespace", Name: "pod0"}},
{ObjectMeta: metav1.ObjectMeta{Namespace: "namespace", Name: "pod1"}},
}
return []isPodMoveCompleteTestCase{
{
Name: "01-get pod resources error, should return false",
podDevices: nil,
mockErr: errors.New("fake get valid pod resources error"),
podList: podList,
wantIsCompleted: false,
},
{
Name: "02-pod move completed, should return true",
podDevices: map[string]PodDevice{
"namespace_pod0": {
ResourceName: common.HuaweiUnHealthAscend910,
DeviceIds: []string{},
},
"namespace_pod1": {
ResourceName: common.HuaweiUnHealthAscend910,
DeviceIds: []string{"0", "1"},
},
},
mockErr: nil,
podList: podList,
wantIsCompleted: true,
},
}
}
func TestIsPodMoveComplete(t *testing.T) {
testCases := buildIsPodMoveCompleteTestCases()
deviceName := "device0"
ps := &PluginServer{
klt2RealDevMap: map[string]string{
"2": deviceName,
},
}
patch := gomonkey.ApplyGlobalVar(&common.ParamOption, common.Option{UseVolcanoType: true})
defer patch.Reset()
for _, tt := range testCases {
t.Run(tt.Name, func(t *testing.T) {
pr := &PodResource{conn: &grpc.ClientConn{}, client: &FakeClient{}}
patch1 := gomonkey.ApplyMethodReturn(&PodResource{}, "GetPodResource",
tt.podDevices, tt.mockErr)
isCompleted := pr.IsPodMoveComplete(deviceName, tt.podList, ps)
patch1.Reset()
if isCompleted != tt.wantIsCompleted {
t.Errorf("IsPodMoveComplete() isCompleted = %v, "+
"wantIsCompleted = %v", isCompleted, tt.wantIsCompleted)
}
})
}
}