karmada/pkg/resourceinterpreter/configurableinterpreter/luavm/lua_test.go

654 lines
16 KiB
Go

package luavm
import (
"encoding/json"
"reflect"
"testing"
lua "github.com/yuin/gopher-lua"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/utils/pointer"
configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1"
workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
"github.com/karmada-io/karmada/pkg/util/helper"
)
func TestGetReplicas(t *testing.T) {
var replicas int32 = 1
vm := VM{UseOpenLibs: false}
tests := []struct {
name string
deploy *appsv1.Deployment
luaScript string
expected bool
wantReplica int32
wantRequires *workv1alpha2.ReplicaRequirements
}{
{
name: "Test GetReplica",
deploy: &appsv1.Deployment{
TypeMeta: metav1.TypeMeta{
Kind: "Deployment",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "bar",
},
Spec: appsv1.DeploymentSpec{
Replicas: &replicas,
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{},
},
},
},
},
},
expected: true,
luaScript: ` function GetReplicas(desiredObj)
nodeClaim = {}
resourceRequest = {}
result = {}
replica = desiredObj.spec.replicas
result.resourceRequest = desiredObj.spec.template.spec.containers[1].resources.limits
nodeClaim.nodeSelector = desiredObj.spec.template.spec.nodeSelector
nodeClaim.tolerations = desiredObj.spec.template.spec.tolerations
result.nodeClaim = {}
result.nodeClaim = nil
return replica, {}
end`,
wantReplica: 1,
wantRequires: &workv1alpha2.ReplicaRequirements{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
toUnstructured, _ := helper.ToUnstructured(tt.deploy)
replicas, requires, err := vm.GetReplicas(toUnstructured, tt.luaScript)
if err != nil {
t.Errorf(err.Error())
}
if !reflect.DeepEqual(replicas, tt.wantReplica) {
t.Errorf("GetReplicas() got = %v, want %v", replicas, tt.wantReplica)
}
if !reflect.DeepEqual(requires, tt.wantRequires) {
t.Errorf("GetReplicas() got = %v, want %v", requires, tt.wantRequires)
}
})
}
}
func TestReviseDeploymentReplica(t *testing.T) {
tests := []struct {
name string
object *unstructured.Unstructured
replica int32
expected *unstructured.Unstructured
expectError bool
luaScript string
}{
{
name: "Test ReviseDeploymentReplica",
object: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "fake-deployment",
},
"spec": map[string]interface{}{
"replicas": 1,
},
},
},
replica: 3,
expectError: true,
luaScript: `function ReviseReplica(desiredObj, desiredReplica)
desiredObj.spec.replicas = desiredReplica
return desiredObj
end`,
},
{
name: "revise deployment replica",
object: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "fake-deployment",
},
"spec": map[string]interface{}{
"replicas": int64(1),
},
},
},
replica: 3,
expected: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "fake-deployment",
},
"spec": map[string]interface{}{
"replicas": int64(2),
},
},
},
expectError: false,
luaScript: `function ReviseReplica(desiredObj, desiredReplica)
desiredObj.spec.replicas = desiredReplica
return desiredObj
end`,
},
}
vm := VM{UseOpenLibs: false}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
res, err := vm.ReviseReplica(tt.object, int64(tt.replica), tt.luaScript)
if err != nil {
t.Errorf(err.Error())
}
deploy := &appsv1.Deployment{}
err = runtime.DefaultUnstructuredConverter.FromUnstructured(res.UnstructuredContent(), deploy)
if err == nil && *deploy.Spec.Replicas == tt.replica {
t.Log("Success Test")
}
if err != nil {
t.Errorf(err.Error())
}
})
}
}
func TestAggregateDeploymentStatus(t *testing.T) {
statusMap := map[string]interface{}{
"replicas": 0,
"readyReplicas": 1,
"updatedReplicas": 0,
"availableReplicas": 0,
"unavailableReplicas": 0,
}
raw, _ := helper.BuildStatusRawExtension(statusMap)
aggregatedStatusItems := []workv1alpha2.AggregatedStatusItem{
{ClusterName: "member1", Status: raw, Applied: true},
{ClusterName: "member2", Status: raw, Applied: true},
}
oldDeploy := &appsv1.Deployment{
TypeMeta: metav1.TypeMeta{
Kind: "Deployment",
APIVersion: "apps/v1",
},
}
oldDeploy.Status = appsv1.DeploymentStatus{
Replicas: 0, ReadyReplicas: 1, UpdatedReplicas: 0, AvailableReplicas: 0, UnavailableReplicas: 0}
newDeploy := &appsv1.Deployment{Status: appsv1.DeploymentStatus{Replicas: 0, ReadyReplicas: 3, UpdatedReplicas: 0, AvailableReplicas: 0, UnavailableReplicas: 0}}
oldObj, _ := helper.ToUnstructured(oldDeploy)
newObj, _ := helper.ToUnstructured(newDeploy)
tests := []struct {
name string
curObj *unstructured.Unstructured
aggregatedStatusItems []workv1alpha2.AggregatedStatusItem
expectedObj *unstructured.Unstructured
luaScript string
}{
{
name: "Test AggregateDeploymentStatus",
curObj: oldObj,
aggregatedStatusItems: aggregatedStatusItems,
expectedObj: newObj,
luaScript: `function AggregateStatus(desiredObj, statusItems)
for i = 1, #statusItems do
desiredObj.status.readyReplicas = desiredObj.status.readyReplicas + statusItems[i].status.readyReplicas
end
return desiredObj
end`,
},
}
vm := VM{UseOpenLibs: false}
for _, tt := range tests {
actualObj, _ := vm.AggregateStatus(tt.curObj, tt.aggregatedStatusItems, tt.luaScript)
actualDeploy := appsv1.DeploymentStatus{}
err := helper.ConvertToTypedObject(actualObj.Object["status"], &actualDeploy)
if err != nil {
t.Error(err.Error())
}
expectDeploy := appsv1.DeploymentStatus{}
err = helper.ConvertToTypedObject(tt.expectedObj.Object["status"], &expectDeploy)
if err != nil {
t.Error(err.Error())
}
if !reflect.DeepEqual(expectDeploy, actualDeploy) {
t.Errorf("AggregateStatus() got = %v, want %v", actualDeploy, expectDeploy)
}
}
}
func TestHealthDeploymentStatus(t *testing.T) {
var cnt int32 = 2
newDeploy := &appsv1.Deployment{
Spec: appsv1.DeploymentSpec{
Replicas: &cnt,
},
ObjectMeta: metav1.ObjectMeta{
Generation: 1,
},
Status: appsv1.DeploymentStatus{ObservedGeneration: 1, Replicas: 2, ReadyReplicas: 2, UpdatedReplicas: 2, AvailableReplicas: 2}}
newObj, _ := helper.ToUnstructured(newDeploy)
tests := []struct {
name string
curObj *unstructured.Unstructured
expectedObj bool
luaScript string
}{
{
name: "Test HealthDeploymentStatus",
curObj: newObj,
expectedObj: true,
luaScript: `function InterpretHealth(observedObj)
return (observedObj.status.updatedReplicas == observedObj.spec.replicas) and (observedObj.metadata.generation == observedObj.status.observedGeneration)
end `,
},
}
vm := VM{UseOpenLibs: false}
for _, tt := range tests {
flag, err := vm.InterpretHealth(tt.curObj, tt.luaScript)
if err != nil {
t.Error(err.Error())
}
if !reflect.DeepEqual(flag, tt.expectedObj) {
t.Errorf("AggregateStatus() got = %v, want %v", flag, tt.expectedObj)
}
}
}
func TestRetainDeployment(t *testing.T) {
tests := []struct {
name string
desiredObj *unstructured.Unstructured
observedObj *unstructured.Unstructured
expectError bool
luaScript string
}{
{
name: "Test RetainDeployment1",
desiredObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "fake-deployment",
},
"spec": map[string]interface{}{
"replicas": 2,
},
},
},
observedObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "fake-deployment",
},
"spec": map[string]interface{}{
"replicas": int64(2),
},
},
},
expectError: true,
luaScript: "function Retain(desiredObj, observedObj)\n desiredObj = observedObj\n return desiredObj\n end",
},
{
name: "Test RetainDeployment2",
desiredObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "fake-deployment",
},
"spec": map[string]interface{}{
"replicas": int64(1),
},
},
},
observedObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "fake-deployment",
},
"spec": map[string]interface{}{
"replicas": int64(1),
},
},
},
expectError: false,
luaScript: `function Retain(desiredObj, observedObj)
desiredObj = observedObj
return desiredObj
end`,
},
}
vm := VM{UseOpenLibs: false}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
res, err := vm.Retain(tt.desiredObj, tt.observedObj, tt.luaScript)
if err != nil {
t.Errorf(err.Error())
}
if !reflect.DeepEqual(res.UnstructuredContent(), tt.observedObj.Object) {
t.Errorf("Retain() got = %v, want %v", res.UnstructuredContent(), tt.observedObj.Object)
}
})
}
}
func TestStatusReflection(t *testing.T) {
testMap := map[string]interface{}{"key": "value"}
wantRawExtension, _ := helper.BuildStatusRawExtension(testMap)
type args struct {
object *unstructured.Unstructured
}
tests := []struct {
name string
args args
want *runtime.RawExtension
wantErr bool
luaScript string
}{
{
"Test StatusReflection",
args{
&unstructured.Unstructured{
Object: map[string]interface{}{
"status": testMap,
},
},
},
wantRawExtension,
false,
`function ReflectStatus (observedObj)
if observedObj.status == nil then
return nil
end
return observedObj.status
end`,
},
}
vm := VM{UseOpenLibs: false}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := vm.ReflectStatus(tt.args.object, tt.luaScript)
if (err != nil) != tt.wantErr {
t.Errorf("reflectWholeStatus() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("reflectWholeStatus() got = %v, want %v", got, tt.want)
}
})
}
}
func TestGetDeployPodDependencies(t *testing.T) {
newDeploy := appsv1.Deployment{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Deployment",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "test",
},
Spec: appsv1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
ServiceAccountName: "test",
},
},
},
}
newObj, _ := helper.ToUnstructured(&newDeploy)
expect := make([]configv1alpha1.DependentObjectReference, 1)
expect[0] = configv1alpha1.DependentObjectReference{
APIVersion: "v1",
Kind: "ServiceAccount",
Namespace: "test",
Name: "test",
}
tests := []struct {
name string
curObj *unstructured.Unstructured
luaScript string
want []configv1alpha1.DependentObjectReference
}{
{
name: "Get GetDeployPodDependencies",
curObj: newObj,
luaScript: `function GetDependencies(desiredObj)
dependentSas = {}
refs = {}
if desiredObj.spec.template.spec.serviceAccountName ~= '' and desiredObj.spec.template.spec.serviceAccountName ~= 'default' then
dependentSas[desiredObj.spec.template.spec.serviceAccountName] = true
end
local idx = 1
for key, value in pairs(dependentSas) do
dependObj = {}
dependObj.apiVersion = 'v1'
dependObj.kind = 'ServiceAccount'
dependObj.name = key
dependObj.namespace = desiredObj.metadata.namespace
refs[idx] = {}
refs[idx] = dependObj
idx = idx + 1
end
return refs
end`,
want: expect,
},
}
vm := VM{UseOpenLibs: false}
for _, tt := range tests {
res, err := vm.GetDependencies(tt.curObj, tt.luaScript)
if err != nil {
t.Errorf("GetDependencies err %v", err)
}
if !reflect.DeepEqual(res, tt.want) {
t.Errorf("GetDependencies() got = %v, want %v", res, tt.want)
}
}
}
func Test_decodeValue(t *testing.T) {
L := lua.NewState()
type args struct {
value interface{}
}
tests := []struct {
name string
args args
want lua.LValue
wantErr bool
}{
{
name: "nil",
args: args{
value: nil,
},
want: lua.LNil,
},
{
name: "nil pointer",
args: args{
value: (*struct{})(nil),
},
want: lua.LNil,
},
{
name: "int pointer",
args: args{
value: pointer.Int(1),
},
want: lua.LNumber(1),
},
{
name: "int",
args: args{
value: 1,
},
want: lua.LNumber(1),
},
{
name: "uint",
args: args{
value: uint(1),
},
want: lua.LNumber(1),
},
{
name: "float",
args: args{
value: 1.0,
},
want: lua.LNumber(1),
},
{
name: "bool",
args: args{
value: true,
},
want: lua.LBool(true),
},
{
name: "string",
args: args{
value: "foo",
},
want: lua.LString("foo"),
},
{
name: "json number",
args: args{
value: json.Number("1"),
},
want: lua.LString("1"),
},
{
name: "slice",
args: args{
value: []string{"foo", "bar"},
},
want: func() lua.LValue {
v := L.CreateTable(2, 0)
v.Append(lua.LString("foo"))
v.Append(lua.LString("bar"))
return v
}(),
},
{
name: "slice pointer",
args: args{
value: &[]string{"foo", "bar"},
},
want: func() lua.LValue {
v := L.CreateTable(2, 0)
v.Append(lua.LString("foo"))
v.Append(lua.LString("bar"))
return v
}(),
},
{
name: "struct",
args: args{
value: struct {
Foo string
}{
Foo: "foo",
},
},
want: func() lua.LValue {
v := L.CreateTable(0, 1)
v.RawSetString("Foo", lua.LString("foo"))
return v
}(),
},
{
name: "struct pointer",
args: args{
value: &struct {
Foo string
}{
Foo: "foo",
},
},
want: func() lua.LValue {
v := L.CreateTable(0, 1)
v.RawSetString("Foo", lua.LString("foo"))
return v
}(),
},
{
name: "[]interface{}",
args: args{
value: []interface{}{1, 2},
},
want: func() lua.LValue {
v := L.CreateTable(2, 0)
v.Append(lua.LNumber(1))
v.Append(lua.LNumber(2))
return v
}(),
},
{
name: "map[string]interface{}",
args: args{
value: map[string]interface{}{
"foo": "foo1",
},
},
want: func() lua.LValue {
v := L.CreateTable(0, 1)
v.RawSetString("foo", lua.LString("foo1"))
return v
}(),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := decodeValue(L, tt.args.value)
if (err != nil) != tt.wantErr {
t.Errorf("decodeValue() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("decodeValue() got = %v, want %v", got, tt.want)
}
})
}
}