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

790 lines
21 KiB
Go

/*
Copyright 2022 The Karmada Authors.
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 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/ptr"
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: ptr.To[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: ptr.To[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.Error(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.Error(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: appsv1.SchemeGroupVersion.String(),
},
Status: appsv1.DeploymentStatus{Replicas: 0, ReadyReplicas: 1, UpdatedReplicas: 0, AvailableReplicas: 0, UnavailableReplicas: 0},
}
newDeploy := &appsv1.Deployment{
TypeMeta: metav1.TypeMeta{
Kind: "Deployment",
APIVersion: appsv1.SchemeGroupVersion.String(),
},
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{
TypeMeta: metav1.TypeMeta{
Kind: "Deployment",
APIVersion: appsv1.SchemeGroupVersion.String(),
},
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.Error(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 ~= nil 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: ptr.To[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)
}
})
}
}