1
0
mirror of https://github.com/rancher/types.git synced 2025-04-28 10:33:18 +00:00
types/mapper/scheduling.go

281 lines
7.9 KiB
Go

package mapper
import (
"fmt"
"sort"
"strings"
"regexp"
"github.com/rancher/norman/types"
"github.com/rancher/norman/types/convert"
"github.com/rancher/norman/types/values"
"k8s.io/api/core/v1"
)
var (
exprRegexp = regexp.MustCompile("^(.*?)\\s*(=|!=|<|>| in | notin )\\s*(.*)$")
)
type SchedulingMapper struct {
}
func (s SchedulingMapper) FromInternal(data map[string]interface{}) {
defer func() {
delete(data, "nodeSelector")
delete(data, "affinity")
}()
var requireAll []string
for key, value := range convert.ToMapInterface(data["nodeSelector"]) {
if value == "" {
requireAll = append(requireAll, key)
} else {
requireAll = append(requireAll, fmt.Sprintf("%s = %s", key, value))
}
}
if len(requireAll) > 0 {
values.PutValue(data, requireAll, "scheduling", "node", "requireAll")
}
v, ok := data["affinity"]
if !ok || v == nil {
return
}
affinity := &v1.Affinity{}
if err := convert.ToObj(v, affinity); err != nil {
return
}
if affinity.NodeAffinity != nil {
s.nodeAffinity(data, affinity.NodeAffinity)
}
}
func (s SchedulingMapper) nodeAffinity(data map[string]interface{}, nodeAffinity *v1.NodeAffinity) {
var requireAll []string
var requireAny []string
var preferred []string
if nodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution != nil {
for _, term := range nodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms {
exprs := NodeSelectorTermToStrings(term)
if len(exprs) == 0 {
continue
}
if len(requireAny) > 0 {
// Once any is set all new terms go to any
requireAny = append(requireAny, strings.Join(exprs, " && "))
} else if len(requireAll) > 0 {
// If all is already set, we actually need to move everything to any
requireAny = append(requireAny, strings.Join(requireAll, " && "))
requireAny = append(requireAny, strings.Join(exprs, " && "))
requireAll = []string{}
} else {
// The first term is considered all
requireAll = exprs
}
}
}
if nodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution != nil {
sortPreferred(nodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution)
for _, term := range nodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution {
exprs := NodeSelectorTermToStrings(term.Preference)
preferred = append(preferred, strings.Join(exprs, " && "))
}
}
if len(requireAll) > 0 {
values.PutValue(data, requireAll, "scheduling", "node", "requireAll")
}
if len(requireAny) > 0 {
values.PutValue(data, requireAny, "scheduling", "node", "requireAny")
}
if len(preferred) > 0 {
values.PutValue(data, preferred, "scheduling", "node", "preferred")
}
}
func sortPreferred(terms []v1.PreferredSchedulingTerm) {
sort.Slice(terms, func(i, j int) bool {
return terms[i].Weight > terms[j].Weight
})
}
func NodeSelectorTermToStrings(term v1.NodeSelectorTerm) []string {
exprs := []string{}
for _, expr := range term.MatchExpressions {
nextExpr := ""
switch expr.Operator {
case v1.NodeSelectorOpIn:
if len(expr.Values) > 1 {
nextExpr = fmt.Sprintf("%s in (%s)", expr.Key, strings.Join(expr.Values, ", "))
} else if len(expr.Values) == 1 {
nextExpr = fmt.Sprintf("%s = %s", expr.Key, expr.Values[0])
}
case v1.NodeSelectorOpNotIn:
if len(expr.Values) > 1 {
nextExpr = fmt.Sprintf("%s notin (%s)", expr.Key, strings.Join(expr.Values, ", "))
} else if len(expr.Values) == 1 {
nextExpr = fmt.Sprintf("%s != %s", expr.Key, expr.Values[0])
}
case v1.NodeSelectorOpExists:
nextExpr = expr.Key
case v1.NodeSelectorOpDoesNotExist:
nextExpr = "!" + expr.Key
case v1.NodeSelectorOpGt:
if len(expr.Values) == 1 {
nextExpr = fmt.Sprintf("%s > %s", expr.Key, expr.Values[0])
}
case v1.NodeSelectorOpLt:
if len(expr.Values) == 1 {
nextExpr = fmt.Sprintf("%s < %s", expr.Key, expr.Values[0])
}
}
if nextExpr != "" {
exprs = append(exprs, nextExpr)
}
}
return exprs
}
func StringsToNodeSelectorTerm(exprs []string) []v1.NodeSelectorTerm {
result := []v1.NodeSelectorTerm{}
for _, inter := range exprs {
term := v1.NodeSelectorTerm{}
for _, expr := range strings.Split(inter, "&&") {
groups := exprRegexp.FindStringSubmatch(expr)
selectorRequirement := v1.NodeSelectorRequirement{}
if groups == nil {
if strings.HasPrefix(expr, "!") {
selectorRequirement.Key = strings.TrimSpace(expr[1:])
selectorRequirement.Operator = v1.NodeSelectorOpDoesNotExist
} else {
selectorRequirement.Key = strings.TrimSpace(expr)
selectorRequirement.Operator = v1.NodeSelectorOpExists
}
} else {
selectorRequirement.Key = strings.TrimSpace(groups[1])
selectorRequirement.Values = convert.ToValuesSlice(groups[3])
op := strings.TrimSpace(groups[2])
switch op {
case "=":
selectorRequirement.Operator = v1.NodeSelectorOpIn
case "!=":
selectorRequirement.Operator = v1.NodeSelectorOpNotIn
case "notin":
selectorRequirement.Operator = v1.NodeSelectorOpNotIn
case "in":
selectorRequirement.Operator = v1.NodeSelectorOpIn
case "<":
selectorRequirement.Operator = v1.NodeSelectorOpLt
case ">":
selectorRequirement.Operator = v1.NodeSelectorOpGt
}
}
term.MatchExpressions = append(term.MatchExpressions, selectorRequirement)
}
result = append(result, term)
}
return result
}
func (s SchedulingMapper) ToInternal(data map[string]interface{}) error {
defer func() {
delete(data, "scheduling")
}()
nodeName := convert.ToString(values.GetValueN(data, "scheduling", "node", "nodeId"))
if nodeName != "" {
data["nodeName"] = nodeName
}
requireAllV := values.GetValueN(data, "scheduling", "node", "requireAll")
requireAnyV := values.GetValueN(data, "scheduling", "node", "requireAny")
preferredV := values.GetValueN(data, "scheduling", "node", "preferred")
if requireAllV == nil && requireAnyV == nil && preferredV == nil {
return nil
}
requireAll := convert.ToStringSlice(requireAllV)
requireAny := convert.ToStringSlice(requireAnyV)
preferred := convert.ToStringSlice(preferredV)
if len(requireAll) == 0 && len(requireAny) == 0 && len(preferred) == 0 {
values.PutValue(data, nil, "affinity", "nodeAffinity")
return nil
}
nodeAffinity := v1.NodeAffinity{}
if len(requireAll) > 0 {
nodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution = &v1.NodeSelector{
NodeSelectorTerms: []v1.NodeSelectorTerm{
AggregateTerms(StringsToNodeSelectorTerm(requireAll)),
},
}
}
if len(requireAny) > 0 {
if nodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution == nil {
nodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution = &v1.NodeSelector{}
}
nodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms = StringsToNodeSelectorTerm(requireAny)
}
if len(preferred) > 0 {
count := int32(100)
for _, term := range StringsToNodeSelectorTerm(preferred) {
nodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution = append(
nodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution, v1.PreferredSchedulingTerm{
Weight: count,
Preference: term,
})
count--
}
}
affinity, _ := convert.EncodeToMap(&v1.Affinity{
NodeAffinity: &nodeAffinity,
})
if nodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution == nil {
values.PutValue(affinity, nil, "nodeAffinity", "preferredDuringSchedulingIgnoredDuringExecution")
}
if nodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution == nil {
values.PutValue(affinity, nil, "nodeAffinity", "requiredDuringSchedulingIgnoredDuringExecution")
}
data["affinity"] = affinity
return nil
}
func AggregateTerms(terms []v1.NodeSelectorTerm) v1.NodeSelectorTerm {
result := v1.NodeSelectorTerm{}
for _, term := range terms {
result.MatchExpressions = append(result.MatchExpressions, term.MatchExpressions...)
}
return result
}
func (s SchedulingMapper) ModifySchema(schema *types.Schema, schemas *types.Schemas) error {
delete(schema.ResourceFields, "nodeSelector")
delete(schema.ResourceFields, "affinity")
return nil
}