529 lines
16 KiB
Go
529 lines
16 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 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 = {}
|
|
serviceAccountName = desiredObj.spec.template.spec.serviceAccountName
|
|
if serviceAccountName ~= nil and serviceAccountName ~= "default" then
|
|
dependency = {}
|
|
dependency.apiVersion = "v1"
|
|
dependency.kind = "ServiceAccount"
|
|
dependency.name = serviceAccountName
|
|
dependency.namespace = desiredObj.metadata.namespace
|
|
dependencies[1] = 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
|
|
}
|