Skip to content

Commit c08ce42

Browse files
committed
Implement interpretComponent for karmadactl interpret
Signed-off-by: wei-chenglai <[email protected]>
1 parent 997a0a1 commit c08ce42

File tree

10 files changed

+233
-26
lines changed

10 files changed

+233
-26
lines changed

api/openapi-spec/swagger.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18477,7 +18477,7 @@
1847718477
],
1847818478
"properties": {
1847918479
"luaScript": {
18480-
"description": "LuaScript holds the Lua script that is used to extract the desired replica count and resource requirements for each component of the resource.\n\nThe script should implement a function as follows:\n\n```\n luaScript: \u003e\n function GetComponents(desiredObj)\n local components = {}\n\n local jobManagerComponent = {\n name = \"jobmanager\"\n replicas = desiredObj.spec.jobManager.replicas\n }\n table.insert(components, jobManagerComponent)\n\n local taskManagerComponent = {\n name = \"taskmanager\"\n replicas = desiredObj.spec.taskManager.replicas\n }\n table.insert(components, taskManagerComponent)\n\n return components\n end\n```\n\nThe content of the LuaScript needs to be a whole function including both declaration and implementation.\n\nThe parameters will be supplied by the system:\n - desiredObj: the object represents the configuration to be applied\n to the member cluster.\n\nThe function expects one return value:\n - components: the resource requirements for each component.\nThe returned value will be set into a ResourceBinding or ClusterResourceBinding.",
18480+
"description": "LuaScript holds the Lua script that is used to extract the desired replica count and resource requirements for each component of the resource.\n\nThe script should implement a function as follows:\n\n```\n luaScript: \u003e\n function GetComponents(desiredObj)\n local components = {}\n\n local jobManagerComponent = {\n name = \"jobmanager\",\n replicas = desiredObj.spec.jobManager.replicas\n }\n table.insert(components, jobManagerComponent)\n\n local taskManagerComponent = {\n name = \"taskmanager\",\n replicas = desiredObj.spec.taskManager.replicas\n }\n table.insert(components, taskManagerComponent)\n\n return components\n end\n```\n\nThe content of the LuaScript needs to be a whole function including both declaration and implementation.\n\nThe parameters will be supplied by the system:\n - desiredObj: the object represents the configuration to be applied\n to the member cluster.\n\nThe function expects one return value:\n - components: the resource requirements for each component.\nThe returned value will be set into a ResourceBinding or ClusterResourceBinding.",
1848118481
"type": "string",
1848218482
"default": ""
1848318483
}

charts/karmada/_crds/bases/config/config.karmada.io_resourceinterpretercustomizations.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,13 @@ spec:
8484
local components = {}
8585
8686
local jobManagerComponent = {
87-
name = "jobmanager"
87+
name = "jobmanager",
8888
replicas = desiredObj.spec.jobManager.replicas
8989
}
9090
table.insert(components, jobManagerComponent)
9191
9292
local taskManagerComponent = {
93-
name = "taskmanager"
93+
name = "taskmanager",
9494
replicas = desiredObj.spec.taskManager.replicas
9595
}
9696
table.insert(components, taskManagerComponent)

pkg/apis/config/v1alpha1/resourceinterpretercustomization_types.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,13 +230,13 @@ type ComponentResourceRequirement struct {
230230
// local components = {}
231231
//
232232
// local jobManagerComponent = {
233-
// name = "jobmanager"
233+
// name = "jobmanager",
234234
// replicas = desiredObj.spec.jobManager.replicas
235235
// }
236236
// table.insert(components, jobManagerComponent)
237237
//
238238
// local taskManagerComponent = {
239-
// name = "taskmanager"
239+
// name = "taskmanager",
240240
// replicas = desiredObj.spec.taskManager.replicas
241241
// }
242242
// table.insert(components, taskManagerComponent)

pkg/generated/openapi/zz_generated.openapi.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/karmadactl/interpret/check_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ TARGET: apps/v1 Deployment
6666
RULERS:
6767
Retain: PASS
6868
InterpretReplica: ERROR: <string> line:1(column:10) near 'format': parse error
69+
InterpretComponent: UNSET
6970
ReviseReplica: UNSET
7071
InterpretStatus: UNSET
7172
AggregateStatus: UNSET
@@ -87,6 +88,7 @@ TARGET: apps/v1 Deployment
8788
RULERS:
8889
Retain: PASS
8990
InterpretReplica: PASS
91+
InterpretComponent: PASS
9092
ReviseReplica: PASS
9193
InterpretStatus: PASS
9294
AggregateStatus: PASS

pkg/karmadactl/interpret/execute_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,23 @@ status: {}
8181
# [2/2] requires:
8282
resourceRequest:
8383
cpu: 100m
84+
`,
85+
},
86+
{
87+
name: "execute interpretComponent",
88+
options: &Options{
89+
FilenameOptions: resource.FilenameOptions{Filenames: []string{"./testdata/customization.yml"}},
90+
Operation: "interpretComponent",
91+
ObservedFile: "./testdata/observed.yml",
92+
Rules: interpreter.AllResourceInterpreterCustomizationRules,
93+
},
94+
want: `---
95+
# [1/1] components:
96+
- name: master
97+
replicaRequirements:
98+
resourceRequest:
99+
cpu: 100m
100+
replicas: 3
84101
`,
85102
},
86103
{

pkg/karmadactl/interpret/interpret.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ var (
6363
# Execute the replicaResource rule
6464
%[1]s interpret -f customization.yml --operation interpretReplica --observed-file observed.yml
6565
66+
# Execute the componentResource rule
67+
%[1]s interpret -f customization.yml --operation interpretComponent --observed-file observed.yml
68+
6669
# Execute the replicaRevision rule
6770
%[1]s interpret -f customization.yml --operation reviseReplica --observed-file observed.yml --desired-replica 2
6871

pkg/karmadactl/interpret/testdata/customization.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,25 @@ spec:
2626
}
2727
return replica, requirement
2828
end
29+
componentResource:
30+
luaScript: >
31+
function GetComponents(obj)
32+
local components = {}
33+
local replica = obj.spec.replicas
34+
local requirement = {
35+
resourceRequest = obj.spec.template.spec.containers[1].resources.limits,
36+
nodeClaim = {
37+
nodeSelector = obj.spec.template.spec.nodeSelector,
38+
tolerations = obj.spec.template.spec.tolerations
39+
}
40+
}
41+
table.insert(components, {
42+
name = "master",
43+
replicas = replica,
44+
replicaRequirements = requirement
45+
})
46+
return components
47+
end
2948
replicaRevision:
3049
luaScript: >
3150
function ReviseReplica(obj, desiredReplica)

pkg/util/interpreter/rule.go

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
var AllResourceInterpreterCustomizationRules = []Rule{
3232
&retentionRule{},
3333
&replicaResourceRule{},
34+
&componentResourceRule{},
3435
&replicaRevisionRule{},
3536
&statusReflectionRule{},
3637
&statusAggregationRule{},
@@ -102,12 +103,13 @@ func (r *replicaResourceRule) Document() string {
102103
return `This rule is used to discover the resource's replica as well as resource requirements.
103104
The script should implement a function as follows:
104105
function GetReplicas(desiredObj)
105-
replica = desiredObj.spec.replicas
106-
nodeClaim = {}
107-
nodeClaim.hardNodeAffinity = {}
108-
nodeClaim.nodeSelector = {}
109-
nodeClaim.tolerations = {}
110-
return replica, nodeClaim
106+
local replica = desiredObj.spec.replicas
107+
local requirement = {}
108+
requirement.nodeClaim = {}
109+
requirement.nodeClaim.nodeSelector = desiredObj.spec.template.spec.nodeSelector
110+
requirement.nodeClaim.tolerations = desiredObj.spec.template.spec.tolerations
111+
requirement.resourceRequest = desiredObj.spec.template.spec.containers[1].resources.limits
112+
return replica, requirement
111113
end`
112114
}
113115

@@ -145,6 +147,66 @@ func (r *replicaResourceRule) Run(interpreter *declarative.ConfigurableInterpret
145147
return newRuleResult().add("replica", replica).add("requires", requires)
146148
}
147149

150+
type componentResourceRule struct {
151+
}
152+
153+
func (r *componentResourceRule) Name() string {
154+
return string(configv1alpha1.InterpreterOperationInterpretComponent)
155+
}
156+
157+
func (r *componentResourceRule) Document() string {
158+
return `This rule is used to discover the resource's components.
159+
The script should implement a function as follows:
160+
function GetComponents(desiredObj)
161+
local components = {}
162+
local jobManagerComponent = {
163+
name = "jobmanager",
164+
replicas = desiredObj.spec.jobManager.replicas
165+
}
166+
table.insert(components, jobManagerComponent)
167+
local taskManagerComponent = {
168+
name = "taskmanager",
169+
replicas = desiredObj.spec.taskManager.replicas
170+
}
171+
table.insert(components, taskManagerComponent)
172+
return components
173+
end`
174+
}
175+
176+
func (r *componentResourceRule) GetScript(c *configv1alpha1.ResourceInterpreterCustomization) string {
177+
if c.Spec.Customizations.ComponentResource != nil {
178+
return c.Spec.Customizations.ComponentResource.LuaScript
179+
}
180+
return ""
181+
}
182+
183+
func (r *componentResourceRule) SetScript(c *configv1alpha1.ResourceInterpreterCustomization, script string) {
184+
if script == "" {
185+
c.Spec.Customizations.ComponentResource = nil
186+
return
187+
}
188+
189+
if c.Spec.Customizations.ComponentResource == nil {
190+
c.Spec.Customizations.ComponentResource = &configv1alpha1.ComponentResourceRequirement{}
191+
}
192+
c.Spec.Customizations.ComponentResource.LuaScript = script
193+
}
194+
195+
func (r *componentResourceRule) Run(interpreter *declarative.ConfigurableInterpreter, args RuleArgs) *RuleResult {
196+
obj, err := args.getObjectOrError()
197+
if err != nil {
198+
return newRuleResultWithError(err)
199+
}
200+
components, enabled, err := interpreter.GetComponents(obj)
201+
if err != nil {
202+
return newRuleResultWithError(err)
203+
}
204+
if !enabled {
205+
return newRuleResultWithError(fmt.Errorf("rule is not enabled"))
206+
}
207+
return newRuleResult().add("components", components)
208+
}
209+
148210
type replicaRevisionRule struct {
149211
}
150212

pkg/util/interpreter/rule_test.go

Lines changed: 118 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -152,12 +152,13 @@ func TestReplicaResourceRule_Document(t *testing.T) {
152152
expected := `This rule is used to discover the resource's replica as well as resource requirements.
153153
The script should implement a function as follows:
154154
function GetReplicas(desiredObj)
155-
replica = desiredObj.spec.replicas
156-
nodeClaim = {}
157-
nodeClaim.hardNodeAffinity = {}
158-
nodeClaim.nodeSelector = {}
159-
nodeClaim.tolerations = {}
160-
return replica, nodeClaim
155+
local replica = desiredObj.spec.replicas
156+
local requirement = {}
157+
requirement.nodeClaim = {}
158+
requirement.nodeClaim.nodeSelector = desiredObj.spec.template.spec.nodeSelector
159+
requirement.nodeClaim.tolerations = desiredObj.spec.template.spec.tolerations
160+
requirement.resourceRequest = desiredObj.spec.template.spec.containers[1].resources.limits
161+
return replica, requirement
161162
end`
162163
assert.Equal(t, expected, rule.Document())
163164
}
@@ -200,24 +201,28 @@ func TestReplicaResourceRule_GetScript(t *testing.T) {
200201
}
201202

202203
func TestReplicaResourceRule_SetScript(t *testing.T) {
203-
c := &configv1alpha1.ResourceInterpreterCustomization{
204-
Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{
205-
Customizations: configv1alpha1.CustomizationRules{
206-
ReplicaResource: &configv1alpha1.ReplicaResourceRequirement{},
207-
},
208-
},
209-
}
210204
r := &replicaResourceRule{}
211205

212206
t.Run("Empty Script", func(t *testing.T) {
207+
c := &configv1alpha1.ResourceInterpreterCustomization{
208+
Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{
209+
Customizations: configv1alpha1.CustomizationRules{
210+
ReplicaResource: &configv1alpha1.ReplicaResourceRequirement{},
211+
},
212+
},
213+
}
213214
r.SetScript(c, "")
214215
if c.Spec.Customizations.ReplicaResource != nil {
215216
t.Errorf("Expected ReplicaResource to be nil, but got %v", c.Spec.Customizations.ReplicaResource)
216217
}
217218
})
218219

219220
t.Run("Non-Empty Script", func(t *testing.T) {
220-
c.Spec.Customizations.ReplicaResource = nil
221+
c := &configv1alpha1.ResourceInterpreterCustomization{
222+
Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{
223+
Customizations: configv1alpha1.CustomizationRules{},
224+
},
225+
}
221226
script := "test script"
222227
r.SetScript(c, script)
223228
if c.Spec.Customizations.ReplicaResource == nil {
@@ -229,6 +234,105 @@ func TestReplicaResourceRule_SetScript(t *testing.T) {
229234
})
230235
}
231236

237+
func TestComponentResourceRule_Name(t *testing.T) {
238+
r := &componentResourceRule{}
239+
expected := string(configv1alpha1.InterpreterOperationInterpretComponent)
240+
actual := r.Name()
241+
assert.Equal(t, expected, actual, "Name should return %v", expected)
242+
}
243+
244+
func TestComponentResourceRule_Document(t *testing.T) {
245+
rule := &componentResourceRule{}
246+
expected := `This rule is used to discover the resource's components.
247+
The script should implement a function as follows:
248+
function GetComponents(desiredObj)
249+
local components = {}
250+
local jobManagerComponent = {
251+
name = "jobmanager",
252+
replicas = desiredObj.spec.jobManager.replicas
253+
}
254+
table.insert(components, jobManagerComponent)
255+
local taskManagerComponent = {
256+
name = "taskmanager",
257+
replicas = desiredObj.spec.taskManager.replicas
258+
}
259+
table.insert(components, taskManagerComponent)
260+
return components
261+
end`
262+
assert.Equal(t, expected, rule.Document())
263+
}
264+
265+
func TestComponentResourceRule_GetScript(t *testing.T) {
266+
t.Run("WithScript", func(t *testing.T) {
267+
c := &configv1alpha1.ResourceInterpreterCustomization{
268+
Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{
269+
Customizations: configv1alpha1.CustomizationRules{
270+
ComponentResource: &configv1alpha1.ComponentResourceRequirement{
271+
LuaScript: "return 'test script'",
272+
},
273+
},
274+
},
275+
}
276+
r := &componentResourceRule{}
277+
script := r.GetScript(c)
278+
expectedScript := c.Spec.Customizations.ComponentResource.LuaScript
279+
if script != expectedScript {
280+
t.Errorf("GetScript() returned %q, expected %q", script, expectedScript)
281+
}
282+
})
283+
284+
t.Run("WithoutScript", func(t *testing.T) {
285+
c := &configv1alpha1.ResourceInterpreterCustomization{
286+
Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{
287+
Customizations: configv1alpha1.CustomizationRules{
288+
ComponentResource: nil,
289+
},
290+
},
291+
}
292+
293+
rule := &componentResourceRule{}
294+
script := rule.GetScript(c)
295+
296+
if script != "" {
297+
t.Errorf("GetScript() returned %q, expected an empty string", script)
298+
}
299+
})
300+
}
301+
302+
func TestComponentResourceRule_SetScript(t *testing.T) {
303+
r := &componentResourceRule{}
304+
305+
t.Run("Empty Script", func(t *testing.T) {
306+
c := &configv1alpha1.ResourceInterpreterCustomization{
307+
Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{
308+
Customizations: configv1alpha1.CustomizationRules{
309+
ComponentResource: &configv1alpha1.ComponentResourceRequirement{},
310+
},
311+
},
312+
}
313+
r.SetScript(c, "")
314+
if c.Spec.Customizations.ComponentResource != nil {
315+
t.Errorf("Expected ComponentResource to be nil, but got %v", c.Spec.Customizations.ComponentResource)
316+
}
317+
})
318+
319+
t.Run("Non-Empty Script", func(t *testing.T) {
320+
c := &configv1alpha1.ResourceInterpreterCustomization{
321+
Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{
322+
Customizations: configv1alpha1.CustomizationRules{},
323+
},
324+
}
325+
script := "test script"
326+
r.SetScript(c, script)
327+
if c.Spec.Customizations.ComponentResource == nil {
328+
t.Errorf("Expected ComponentResource to be non-nil, but got nil")
329+
}
330+
if c.Spec.Customizations.ComponentResource.LuaScript != "test script" {
331+
t.Errorf("Expected ComponentResource.LuaScript to be %s, but got %s", script, c.Spec.Customizations.ComponentResource.LuaScript)
332+
}
333+
})
334+
}
335+
232336
func TestReplicaRevisionRule_Name(t *testing.T) {
233337
r := &replicaRevisionRule{}
234338
expected := string(configv1alpha1.InterpreterOperationReviseReplica)

0 commit comments

Comments
 (0)