karmada/pkg/util/interpreter/rule.go

513 lines
15 KiB
Go

package interpreter
import (
"fmt"
"strings"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
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/resourceinterpreter/customized/declarative"
)
// AllResourceInterpreterCustomizationRules all InterpreterOperations
var AllResourceInterpreterCustomizationRules = []Rule{
&retentionRule{},
&replicaResourceRule{},
&replicaRevisionRule{},
&statusReflectionRule{},
&statusAggregationRule{},
&healthInterpretationRule{},
&dependencyInterpretationRule{},
}
type retentionRule struct{}
func (r *retentionRule) Name() string {
return string(configv1alpha1.InterpreterOperationRetain)
}
func (r *retentionRule) Document() string {
return `This rule is used to retain runtime values to the desired specification.
The script should implement a function as follows:
function Retain(desiredObj, observedObj)
desiredObj.spec.fieldFoo = observedObj.spec.fieldFoo
return desiredObj
end`
}
func (r *retentionRule) GetScript(c *configv1alpha1.ResourceInterpreterCustomization) string {
if c.Spec.Customizations.Retention != nil {
return c.Spec.Customizations.Retention.LuaScript
}
return ""
}
func (r *retentionRule) SetScript(c *configv1alpha1.ResourceInterpreterCustomization, script string) {
if script == "" {
c.Spec.Customizations.Retention = nil
return
}
if c.Spec.Customizations.Retention == nil {
c.Spec.Customizations.Retention = &configv1alpha1.LocalValueRetention{}
}
c.Spec.Customizations.Retention.LuaScript = script
}
func (r *retentionRule) Run(interpreter *declarative.ConfigurableInterpreter, args RuleArgs) *RuleResult {
desired, err := args.getDesiredObjectOrError()
if err != nil {
return newRuleResultWithError(err)
}
observed, err := args.getObservedObjectOrError()
if err != nil {
return newRuleResultWithError(err)
}
retained, enabled, err := interpreter.Retain(desired, observed)
if err != nil {
return newRuleResultWithError(err)
}
if !enabled {
return newRuleResultWithError(fmt.Errorf("rule is not enabled"))
}
return newRuleResult().add("retained", retained)
}
type replicaResourceRule struct {
}
func (r *replicaResourceRule) Name() string {
return string(configv1alpha1.InterpreterOperationInterpretReplica)
}
func (r *replicaResourceRule) Document() string {
return `This rule is used to discover the resource's replica as well as resource requirements.
The script should implement a function as follows:
function GetReplicas(desiredObj)
replica = desiredObj.spec.replicas
nodeClaim = {}
nodeClaim.hardNodeAffinity = {}
nodeClaim.nodeSelector = {}
nodeClaim.tolerations = {}
return replica, nodeClaim
end`
}
func (r *replicaResourceRule) GetScript(c *configv1alpha1.ResourceInterpreterCustomization) string {
if c.Spec.Customizations.ReplicaResource != nil {
return c.Spec.Customizations.ReplicaResource.LuaScript
}
return ""
}
func (r *replicaResourceRule) SetScript(c *configv1alpha1.ResourceInterpreterCustomization, script string) {
if script == "" {
c.Spec.Customizations.ReplicaResource = nil
return
}
if c.Spec.Customizations.ReplicaResource == nil {
c.Spec.Customizations.ReplicaResource = &configv1alpha1.ReplicaResourceRequirement{}
}
c.Spec.Customizations.ReplicaResource.LuaScript = script
}
func (r *replicaResourceRule) Run(interpreter *declarative.ConfigurableInterpreter, args RuleArgs) *RuleResult {
obj, err := args.getObjectOrError()
if err != nil {
return newRuleResultWithError(err)
}
replica, requires, enabled, err := interpreter.GetReplicas(obj)
if err != nil {
return newRuleResultWithError(err)
}
if !enabled {
return newRuleResultWithError(fmt.Errorf("rule is not enabled"))
}
return newRuleResult().add("replica", replica).add("requires", requires)
}
type replicaRevisionRule struct {
}
func (r *replicaRevisionRule) Name() string {
return string(configv1alpha1.InterpreterOperationReviseReplica)
}
func (r *replicaRevisionRule) Document() string {
return `This rule is used to revise replicas in the desired specification.
The script should implement a function as follows:
function ReviseReplica(desiredObj, desiredReplica)
desiredObj.spec.replicas = desiredReplica
return desiredObj
end`
}
func (r *replicaRevisionRule) GetScript(c *configv1alpha1.ResourceInterpreterCustomization) string {
if c.Spec.Customizations.ReplicaRevision != nil {
return c.Spec.Customizations.ReplicaRevision.LuaScript
}
return ""
}
func (r *replicaRevisionRule) SetScript(c *configv1alpha1.ResourceInterpreterCustomization, script string) {
if script == "" {
c.Spec.Customizations.ReplicaRevision = nil
return
}
if c.Spec.Customizations.ReplicaRevision == nil {
c.Spec.Customizations.ReplicaRevision = &configv1alpha1.ReplicaRevision{}
}
c.Spec.Customizations.ReplicaRevision.LuaScript = script
}
func (r *replicaRevisionRule) Run(interpreter *declarative.ConfigurableInterpreter, args RuleArgs) *RuleResult {
obj, err := args.getObjectOrError()
if err != nil {
return newRuleResultWithError(err)
}
revised, enabled, err := interpreter.ReviseReplica(obj, args.Replica)
if err != nil {
return newRuleResultWithError(err)
}
if !enabled {
return newRuleResultWithError(fmt.Errorf("rule is not enabled"))
}
return newRuleResult().add("revised", revised)
}
type statusReflectionRule struct {
}
func (s *statusReflectionRule) Name() string {
return string(configv1alpha1.InterpreterOperationInterpretStatus)
}
func (s *statusReflectionRule) Document() string {
return `This rule is used to get the status from the observed specification.
The script should implement a function as follows:
function ReflectStatus(observedObj)
status = {}
status.readyReplicas = observedObj.status.observedObj
return status
end`
}
func (s *statusReflectionRule) GetScript(c *configv1alpha1.ResourceInterpreterCustomization) string {
if c.Spec.Customizations.StatusReflection != nil {
return c.Spec.Customizations.StatusReflection.LuaScript
}
return ""
}
func (s *statusReflectionRule) SetScript(c *configv1alpha1.ResourceInterpreterCustomization, script string) {
if script == "" {
c.Spec.Customizations.StatusReflection = nil
return
}
if c.Spec.Customizations.StatusReflection == nil {
c.Spec.Customizations.StatusReflection = &configv1alpha1.StatusReflection{}
}
c.Spec.Customizations.StatusReflection.LuaScript = script
}
func (s *statusReflectionRule) Run(interpreter *declarative.ConfigurableInterpreter, args RuleArgs) *RuleResult {
obj, err := args.getObjectOrError()
if err != nil {
return newRuleResultWithError(err)
}
status, enabled, err := interpreter.ReflectStatus(obj)
if err != nil {
return newRuleResultWithError(err)
}
if !enabled {
return newRuleResultWithError(fmt.Errorf("rule is not enabled"))
}
return newRuleResult().add("status", status)
}
type statusAggregationRule struct {
}
func (s *statusAggregationRule) Name() string {
return string(configv1alpha1.InterpreterOperationAggregateStatus)
}
func (s *statusAggregationRule) Document() string {
return `This rule is used to aggregate decentralized statuses to the desired specification.
The script should implement a function as follows:
function AggregateStatus(desiredObj, statusItems)
for i = 1, #items do
desiredObj.status.readyReplicas = desiredObj.status.readyReplicas + items[i].readyReplicas
end
return desiredObj
end`
}
func (s *statusAggregationRule) GetScript(c *configv1alpha1.ResourceInterpreterCustomization) string {
if c.Spec.Customizations.StatusAggregation != nil {
return c.Spec.Customizations.StatusAggregation.LuaScript
}
return ""
}
func (s *statusAggregationRule) SetScript(c *configv1alpha1.ResourceInterpreterCustomization, script string) {
if script == "" {
c.Spec.Customizations.StatusAggregation = nil
return
}
if c.Spec.Customizations.StatusAggregation == nil {
c.Spec.Customizations.StatusAggregation = &configv1alpha1.StatusAggregation{}
}
c.Spec.Customizations.StatusAggregation.LuaScript = script
}
func (s *statusAggregationRule) Run(interpreter *declarative.ConfigurableInterpreter, args RuleArgs) *RuleResult {
obj, err := args.getObjectOrError()
if err != nil {
return newRuleResultWithError(err)
}
status := args.Status
if status == nil {
status = []workv1alpha2.AggregatedStatusItem{}
}
aggregateStatus, enabled, err := interpreter.AggregateStatus(obj, status)
if err != nil {
return newRuleResultWithError(err)
}
if !enabled {
return newRuleResultWithError(fmt.Errorf("rule is not enabled"))
}
return newRuleResult().add("aggregatedStatus", aggregateStatus)
}
type healthInterpretationRule struct {
}
func (h *healthInterpretationRule) Name() string {
return string(configv1alpha1.InterpreterOperationInterpretHealth)
}
func (h *healthInterpretationRule) Document() string {
return `This rule is used to assess the health state of a specific resource.
The script should implement a function as follows:
luaScript: >
function InterpretHealth(observedObj)
if observedObj.status.readyReplicas == observedObj.spec.replicas then
return true
end
end`
}
func (h *healthInterpretationRule) GetScript(c *configv1alpha1.ResourceInterpreterCustomization) string {
if c.Spec.Customizations.HealthInterpretation != nil {
return c.Spec.Customizations.HealthInterpretation.LuaScript
}
return ""
}
func (h *healthInterpretationRule) SetScript(c *configv1alpha1.ResourceInterpreterCustomization, script string) {
if script == "" {
c.Spec.Customizations.HealthInterpretation = nil
return
}
if c.Spec.Customizations.HealthInterpretation == nil {
c.Spec.Customizations.HealthInterpretation = &configv1alpha1.HealthInterpretation{}
}
c.Spec.Customizations.HealthInterpretation.LuaScript = script
}
func (h *healthInterpretationRule) Run(interpreter *declarative.ConfigurableInterpreter, args RuleArgs) *RuleResult {
obj, err := args.getObjectOrError()
if err != nil {
return newRuleResultWithError(err)
}
healthy, enabled, err := interpreter.InterpretHealth(obj)
if err != nil {
return newRuleResultWithError(err)
}
if !enabled {
return newRuleResultWithError(fmt.Errorf("rule is not enabled"))
}
return newRuleResult().add("healthy", healthy)
}
type dependencyInterpretationRule struct {
}
func (d *dependencyInterpretationRule) Name() string {
return string(configv1alpha1.InterpreterOperationInterpretDependency)
}
func (d *dependencyInterpretationRule) Document() string {
return ` This rule is used to interpret the dependencies of a specific resource.
The script should implement a function as follows:
function GetDependencies(desiredObj)
dependencies = {}
if desiredObj.spec.serviceAccountName ~= "" and desiredObj.spec.serviceAccountName ~= "default" then
dependency = {}
dependency.apiVersion = "v1"
dependency.kind = "ServiceAccount"
dependency.name = desiredObj.spec.serviceAccountName
dependency.namespace = desiredObj.namespace
dependencies[0] = {}
dependencies[0] = dependency
end
return dependencies
end`
}
func (d *dependencyInterpretationRule) GetScript(c *configv1alpha1.ResourceInterpreterCustomization) string {
if c.Spec.Customizations.DependencyInterpretation != nil {
return c.Spec.Customizations.DependencyInterpretation.LuaScript
}
return ""
}
func (d *dependencyInterpretationRule) SetScript(c *configv1alpha1.ResourceInterpreterCustomization, script string) {
if script == "" {
c.Spec.Customizations.DependencyInterpretation = nil
return
}
if c.Spec.Customizations.DependencyInterpretation == nil {
c.Spec.Customizations.DependencyInterpretation = &configv1alpha1.DependencyInterpretation{}
}
c.Spec.Customizations.DependencyInterpretation.LuaScript = script
}
func (d *dependencyInterpretationRule) Run(interpreter *declarative.ConfigurableInterpreter, args RuleArgs) *RuleResult {
obj, err := args.getObjectOrError()
if err != nil {
return newRuleResultWithError(err)
}
dependencies, enabled, err := interpreter.GetDependencies(obj)
if err != nil {
return newRuleResultWithError(err)
}
if !enabled {
return newRuleResultWithError(fmt.Errorf("rule is not enabled"))
}
return newRuleResult().add("dependencies", dependencies)
}
// Rule known how to get and set script for interpretation rule, and can execute the rule with given args.
type Rule interface {
// Name returns the name of the rule.
Name() string
// Document explains detail of rule.
Document() string
// GetScript returns the script for the rule from customization. If not enabled, return empty
GetScript(*configv1alpha1.ResourceInterpreterCustomization) string
// SetScript set the script for the rule. If script is empty, disable the rule.
SetScript(*configv1alpha1.ResourceInterpreterCustomization, string)
// Run execute the rule with given args, and return the result.
Run(*declarative.ConfigurableInterpreter, RuleArgs) *RuleResult
}
// Rules is a series of rules.
type Rules []Rule
// Names returns the names of containing rules.
func (r Rules) Names() []string {
names := make([]string, len(r))
for i, rr := range r {
names[i] = rr.Name()
}
return names
}
// GetByOperation returns the matched rule by operation name, ignoring case. Return nil if none is matched.
func (r Rules) GetByOperation(operation string) Rule {
if operation == "" {
return nil
}
operation = strings.ToLower(operation)
for _, rule := range r {
ruleName := strings.ToLower(rule.Name())
if ruleName == operation {
return rule
}
}
return nil
}
// Get returns the rule with the name. If not found, return nil.
func (r Rules) Get(name string) Rule {
for _, rr := range r {
if rr.Name() == name {
return rr
}
}
return nil
}
// RuleArgs rule execution args.
type RuleArgs struct {
Desired *unstructured.Unstructured
Observed *unstructured.Unstructured
Status []workv1alpha2.AggregatedStatusItem
Replica int64
}
func (r RuleArgs) getDesiredObjectOrError() (*unstructured.Unstructured, error) {
if r.Desired == nil {
return nil, fmt.Errorf("desired, desired-file options are not set")
}
return r.Desired, nil
}
func (r RuleArgs) getObservedObjectOrError() (*unstructured.Unstructured, error) {
if r.Observed == nil {
return nil, fmt.Errorf("observed, observed-file options are not set")
}
return r.Observed, nil
}
func (r RuleArgs) getObjectOrError() (*unstructured.Unstructured, error) {
if r.Desired == nil && r.Observed == nil {
return nil, fmt.Errorf("desired-file, observed-file options are not set")
}
if r.Desired != nil && r.Observed != nil {
return nil, fmt.Errorf("you can not specify both desired-file and observed-file options")
}
if r.Desired != nil {
return r.Desired, nil
}
return r.Observed, nil
}
// NameValue name and value.
type NameValue struct {
Name string
Value interface{}
}
// RuleResult rule execution result.
type RuleResult struct {
Results []NameValue
Err error
}
func newRuleResult() *RuleResult {
return &RuleResult{}
}
func newRuleResultWithError(err error) *RuleResult {
return &RuleResult{
Err: err,
}
}
func (r *RuleResult) add(name string, value interface{}) *RuleResult {
r.Results = append(r.Results, NameValue{Name: name, Value: value})
return r
}