karmada/pkg/resourceinterpreter/customized/declarative/luavm/lua_test.go

767 lines
20 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"
"k8s.io/apimachinery/pkg/api/resource"
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) {
vm := New(false, 1)
tests := []struct {
name string
deploy *appsv1.Deployment
luaScript string
expected bool
wantReplica int32
wantRequires *workv1alpha2.ReplicaRequirements
}{
{
name: "Test GetReplica with kube.accuratePodRequirements",
deploy: &appsv1.Deployment{
TypeMeta: metav1.TypeMeta{
Kind: "Deployment",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "bar",
},
Spec: appsv1.DeploymentSpec{
Replicas: pointer.Int32(1),
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
NodeSelector: map[string]string{"foo": "foo1"},
Tolerations: []corev1.Toleration{{Key: "bar", Operator: corev1.TolerationOpExists}},
Containers: []corev1.Container{
{Resources: corev1.ResourceRequirements{Limits: map[corev1.ResourceName]resource.Quantity{
corev1.ResourceCPU: resource.MustParse("100m"),
corev1.ResourceMemory: resource.MustParse("100M"),
}}},
{Resources: corev1.ResourceRequirements{Limits: map[corev1.ResourceName]resource.Quantity{
corev1.ResourceCPU: resource.MustParse("1.2"),
corev1.ResourceMemory: resource.MustParse("1.2G"),
}}},
},
},
},
},
},
expected: true,
luaScript: `
local kube = require("kube")
function GetReplicas(desiredObj)
replica = desiredObj.spec.replicas
requires = kube.accuratePodRequirements(desiredObj.spec.template)
return replica, requires
end`,
wantReplica: 1,
wantRequires: &workv1alpha2.ReplicaRequirements{
NodeClaim: &workv1alpha2.NodeClaim{
NodeSelector: map[string]string{"foo": "foo1"},
Tolerations: []corev1.Toleration{{Key: "bar", Operator: corev1.TolerationOpExists}},
},
ResourceRequest: map[corev1.ResourceName]resource.Quantity{
corev1.ResourceCPU: resource.MustParse("1.3"),
corev1.ResourceMemory: resource.MustParse("1.3G"),
},
},
},
{
name: "Test GetReplica with kube.resourceAdd",
deploy: &appsv1.Deployment{
TypeMeta: metav1.TypeMeta{
Kind: "Deployment",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "bar",
},
Spec: appsv1.DeploymentSpec{
Replicas: pointer.Int32(1),
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
NodeSelector: map[string]string{"foo": "foo1"},
Tolerations: []corev1.Toleration{{Key: "bar", Operator: corev1.TolerationOpExists}},
Containers: []corev1.Container{
{Resources: corev1.ResourceRequirements{Limits: map[corev1.ResourceName]resource.Quantity{
corev1.ResourceCPU: resource.MustParse("100m"),
corev1.ResourceMemory: resource.MustParse("100M"),
}}},
{Resources: corev1.ResourceRequirements{Limits: map[corev1.ResourceName]resource.Quantity{
corev1.ResourceCPU: resource.MustParse("1.2"),
corev1.ResourceMemory: resource.MustParse("1.2G"),
}}},
},
},
},
},
},
expected: true,
luaScript: `
local kube = require("kube")
function GetReplicas(desiredObj)
replica = desiredObj.spec.replicas
requires = {
nodeClaim = {
nodeSelector = desiredObj.spec.template.spec.nodeSelector,
tolerations = desiredObj.spec.template.spec.tolerations
},
resourceRequest = {},
}
for i = 1, #desiredObj.spec.template.spec.containers do
requires.resourceRequest.cpu = kube.resourceAdd(requires.resourceRequest.cpu, desiredObj.spec.template.spec.containers[i].resources.limits.cpu)
requires.resourceRequest.memory = kube.resourceAdd(requires.resourceRequest.memory, desiredObj.spec.template.spec.containers[i].resources.limits.memory)
end
return replica, requires
end`,
wantReplica: 1,
wantRequires: &workv1alpha2.ReplicaRequirements{
NodeClaim: &workv1alpha2.NodeClaim{
NodeSelector: map[string]string{"foo": "foo1"},
Tolerations: []corev1.Toleration{{Key: "bar", Operator: corev1.TolerationOpExists}},
},
ResourceRequest: map[corev1.ResourceName]resource.Quantity{
corev1.ResourceCPU: resource.MustParse("1.3"),
corev1.ResourceMemory: resource.MustParse("1.3G"),
},
},
},
}
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.Fatal(err.Error())
}
if !reflect.DeepEqual(replicas, tt.wantReplica) {
t.Errorf("GetReplicas() got = %v, want %v", replicas, tt.wantReplica)
}
if got, want := requires.ResourceRequest.Cpu(), tt.wantRequires.ResourceRequest.Cpu(); !got.Equal(*want) {
t.Errorf("GetReplicas() got Cpu = %s, want %s", got, want)
}
if got, want := requires.ResourceRequest.Memory(), tt.wantRequires.ResourceRequest.Memory(); !got.Equal(*want) {
t.Errorf("GetReplicas() got Memory = %s, want %s", got, want)
}
requires.ResourceRequest, tt.wantRequires.ResourceRequest = nil, nil
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 := New(false, 1)
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 := New(false, 1)
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 := New(false, 1)
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 := New(false, 1)
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 := New(false, 1)
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) {
newDeploy1 := 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",
},
},
},
}
newDeploy2 := newDeploy1.DeepCopy()
newDeploy2.Namespace = ""
newObj1, _ := helper.ToUnstructured(&newDeploy1)
expect1 := make([]configv1alpha1.DependentObjectReference, 1)
expect1[0] = configv1alpha1.DependentObjectReference{
APIVersion: "v1",
Kind: "ServiceAccount",
Namespace: "test",
Name: "test",
}
newObj2, _ := helper.ToUnstructured(&newDeploy2)
expect2 := make([]configv1alpha1.DependentObjectReference, 1)
expect2[0] = configv1alpha1.DependentObjectReference{
APIVersion: "v1",
Kind: "ServiceAccount",
Namespace: "default",
Name: "test",
}
tests := []struct {
name string
curObj *unstructured.Unstructured
luaScript string
want []configv1alpha1.DependentObjectReference
}{
{
name: "Get GetDeployPodDependencies",
curObj: newObj1,
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: expect1,
},
{
name: "Test getPodDependencies",
curObj: newObj1,
luaScript: `local kube = require("kube")
function GetDependencies(desiredObj)
dependencies = kube.getPodDependencies(desiredObj.spec.template, desiredObj.metadata.namespace)
return dependencies
end`,
want: expect1,
},
{
name: "Test getPodDependencies when desiredObj's namespace is empty",
curObj: newObj2,
luaScript: `local kube = require("kube")
function GetDependencies(desiredObj)
dependencies = kube.getPodDependencies(desiredObj.spec.template, desiredObj.metadata.namespace)
return dependencies
end`,
want: expect2,
},
}
vm := New(false, 1)
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)
}
})
}
}