Skip to content

Commit

Permalink
Merge pull request #2871 from ikaven1024/feature/lua-resource
Browse files Browse the repository at this point in the history
add kube lib for lua
  • Loading branch information
karmada-bot authored Dec 9, 2022
2 parents c0fc2f8 + 0f5e377 commit 54b7aa2
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 21 deletions.
5 changes: 5 additions & 0 deletions examples/karmadactlinterpret/observed-deploy-nginx.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,14 @@ spec:
labels:
app: nginx
spec:
nodeSelector:
foo: bar
containers:
- image: nginx
name: nginx
resources:
limits:
cpu: 100m
status:
availableReplicas: 2
observedGeneration: 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ spec:
customizations:
replicaResource:
luaScript: >
local kube = require("kube")
function GetReplicas(obj)
replica = obj.spec.replicas
requirement = {}
requirement = kube.accuratePodRequirements(obj.spec.template)
return replica, requirement
end
replicaRevision:
Expand Down
93 changes: 93 additions & 0 deletions pkg/resourceinterpreter/configurableinterpreter/luavm/kube.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package luavm

import (
lua "github.com/yuin/gopher-lua"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"

"github.com/karmada-io/karmada/pkg/util/helper"
)

const (
// KubeLibName is the name of the kube library.
KubeLibName = "kube"
)

// KubeLoader loads the kube library. Import this library before your script by:
//
// local kube = require("kube")
//
// Then you can call functions in this library by:
//
// kube.xxx()
//
// This library Contains:
// - function resourceAdd(r1, r2, ...)
// accruing the quantity of resources. Example:
// cpu = kube.resourceAdd(r1.cpu, r2.cpu, r3.cpu)
// - function accuratePodRequirements(pod) requirements
// accurate total resource requirements for pod. Example:
// requirements = kube.accuratePodRequirements(pod)
func KubeLoader(ls *lua.LState) int {
mod := ls.SetFuncs(ls.NewTable(), kubeFuncs)
ls.Push(mod)
return 1
}

var kubeFuncs = map[string]lua.LGFunction{
"resourceAdd": resourceAdd,
"accuratePodRequirements": accuratePodRequirements,
}

func resourceAdd(ls *lua.LState) int {
res := resource.Quantity{}
n := ls.GetTop()
for i := 1; i <= n; i++ {
q := checkResourceQuantity(ls, i)
res.Add(q)
}

s := res.String()
ls.Push(lua.LString(s))
return 1
}

func accuratePodRequirements(ls *lua.LState) int {
n := ls.GetTop()
if n != 1 {
ls.RaiseError("getPodRequirements only accepts one argument")
return 0
}

v := ls.CheckTable(1)
pod := &corev1.PodTemplateSpec{}
err := ConvertLuaResultInto(v, pod)
if err != nil {
ls.RaiseError("fail to convert lua value %#v to PodTemplateSpec: %v", v, err)
return 0
}

requirements := helper.GenerateReplicaRequirements(pod)
retValue, err := decodeValue(ls, requirements)
if err != nil {
ls.RaiseError("fail to convert %#v to Lua value: %v", requirements, err)
return 0
}

ls.Push(retValue)
return 1
}

func checkResourceQuantity(ls *lua.LState, n int) resource.Quantity {
v := ls.Get(n)
switch typ := v.Type(); typ {
case lua.LTNil:
return resource.Quantity{}
case lua.LTString:
s := string(v.(lua.LString))
return resource.MustParse(s)
default:
ls.TypeError(n, lua.LTString)
return resource.Quantity{}
}
}
4 changes: 4 additions & 0 deletions pkg/resourceinterpreter/configurableinterpreter/luavm/lua.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ func (vm *VM) NewLuaState() (*lua.LState, error) {
}
// preload our 'safe' version of the OS library. Allows the 'local os = require("os")' to work
l.PreloadModule(lua.OsLibName, lifted.SafeOsLoader)
// preload kube library. Allows the 'local kube = require("kube")' to work
l.PreloadModule(KubeLibName, KubeLoader)
return l, err
}

Expand Down Expand Up @@ -279,6 +281,8 @@ func NewWithContext(ctx context.Context) (*lua.LState, error) {
}
// preload our 'safe' version of the OS library. Allows the 'local os = require("os")' to work
l.PreloadModule(lua.OsLibName, lifted.SafeOsLoader)
// preload kube library. Allows the 'local kube = require("kube")' to work
l.PreloadModule(KubeLibName, KubeLoader)
if ctx != nil {
l.SetContext(ctx)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func ConvertLuaResultInto(luaResult lua.LValue, obj interface{}) error {

err = json.Unmarshal(jsonBytes, obj)
if err != nil {
return fmt.Errorf("can not unmarshal %v to %#v", string(jsonBytes), obj)
return fmt.Errorf("can not unmarshal %v to %#v:%v", string(jsonBytes), obj, err)
}
return nil
}
Expand Down
121 changes: 102 additions & 19 deletions pkg/resourceinterpreter/configurableinterpreter/luavm/lua_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
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"
Expand All @@ -19,7 +20,6 @@ import (
)

func TestGetReplicas(t *testing.T) {
var replicas int32 = 1
vm := New(false, 1)
tests := []struct {
name string
Expand All @@ -30,7 +30,7 @@ func TestGetReplicas(t *testing.T) {
wantRequires *workv1alpha2.ReplicaRequirements
}{
{
name: "Test GetReplica",
name: "Test GetReplica with kube.accuratePodRequirements",
deploy: &appsv1.Deployment{
TypeMeta: metav1.TypeMeta{
Kind: "Deployment",
Expand All @@ -41,31 +41,105 @@ func TestGetReplicas(t *testing.T) {
Name: "bar",
},
Spec: appsv1.DeploymentSpec{
Replicas: &replicas,
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: ` 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{},
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"),
},
},
},
}

Expand All @@ -74,11 +148,20 @@ func TestGetReplicas(t *testing.T) {
toUnstructured, _ := helper.ToUnstructured(tt.deploy)
replicas, requires, err := vm.GetReplicas(toUnstructured, tt.luaScript)
if err != nil {
t.Errorf(err.Error())
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)
}
Expand Down

0 comments on commit 54b7aa2

Please sign in to comment.