-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
migrationpolicy.go
122 lines (99 loc) · 4.34 KB
/
migrationpolicy.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
package watch
import (
k8sv1 "k8s.io/api/core/v1"
k6tv1 "kubevirt.io/api/core/v1"
"kubevirt.io/api/migrations/v1alpha1"
)
type migrationPolicyMatchScore struct {
matchingVMILabels int
matchingNSLabels int
}
func (score migrationPolicyMatchScore) equals(otherScore migrationPolicyMatchScore) bool {
return score.matchingVMILabels == otherScore.matchingVMILabels &&
score.matchingNSLabels == otherScore.matchingNSLabels
}
func (score migrationPolicyMatchScore) greaterThan(otherScore migrationPolicyMatchScore) bool {
thisTotalScore := score.matchingNSLabels + score.matchingVMILabels
otherTotalScore := otherScore.matchingNSLabels + otherScore.matchingVMILabels
if thisTotalScore == otherTotalScore {
return score.matchingVMILabels > otherScore.matchingVMILabels
}
return thisTotalScore > otherTotalScore
}
func (score migrationPolicyMatchScore) lessThan(otherScore migrationPolicyMatchScore) bool {
return !score.equals(otherScore) && !score.greaterThan(otherScore)
}
// MatchPolicy returns the policy that is matched to the vmi, or nil of no policy is matched.
//
// Since every policy can specify VMI and Namespace labels to match to, matching is done by returning the most
// detailed policy, meaning the policy that matches the VMI and specifies the most labels that matched either
// the VMI or its namespace labels.
//
// If two policies are matched and have the same level of details (i.e. same number of matching labels) the matched
// policy is chosen by policies' names ordered by lexicographic order. The reason is to create a rather arbitrary yet
// deterministic way of matching policies.
func MatchPolicy(policyList *v1alpha1.MigrationPolicyList, vmi *k6tv1.VirtualMachineInstance, vmiNamespace *k8sv1.Namespace) *v1alpha1.MigrationPolicy {
var mathingPolicies []v1alpha1.MigrationPolicy
bestScore := migrationPolicyMatchScore{}
for _, policy := range policyList.Items {
doesMatch, curScore := countMatchingLabels(&policy, vmi.Labels, vmiNamespace.Labels)
if !doesMatch || curScore.lessThan(bestScore) {
continue
} else if curScore.greaterThan(bestScore) {
bestScore = curScore
mathingPolicies = []v1alpha1.MigrationPolicy{policy}
} else {
mathingPolicies = append(mathingPolicies, policy)
}
}
if len(mathingPolicies) == 0 {
return nil
} else if len(mathingPolicies) == 1 {
return &mathingPolicies[0]
}
// If more than one policy is matched with the same number of matching labels it will be chosen by policies names'
// lexicographic order
firstPolicyNameLexicographicOrder := mathingPolicies[0].Name
var firstPolicyNameLexicographicOrderIdx int
for idx, matchingPolicy := range mathingPolicies {
if matchingPolicy.Name < firstPolicyNameLexicographicOrder {
firstPolicyNameLexicographicOrder = matchingPolicy.Name
firstPolicyNameLexicographicOrderIdx = idx
}
}
return &mathingPolicies[firstPolicyNameLexicographicOrderIdx]
}
// countMatchingLabels checks if a policy matches to a VMI and the number of matching labels.
// In the case that doesMatch is false, matchingLabels needs to be dismissed and not counted on.
func countMatchingLabels(policy *v1alpha1.MigrationPolicy, vmiLabels, namespaceLabels map[string]string) (doesMatch bool, score migrationPolicyMatchScore) {
var matchingVMILabels, matchingNSLabels int
doesMatch = true
if policy.Spec.Selectors == nil {
return false, score
}
countLabelsHelper := func(policyLabels, labelsToMatch map[string]string) (matchingLabels int) {
for policyKey, policyValue := range policyLabels {
value, exists := labelsToMatch[policyKey]
if exists && value == policyValue {
matchingLabels++
} else {
doesMatch = false
return
}
}
return matchingLabels
}
areSelectorsAndLabelsNotNil := func(selector v1alpha1.LabelSelector, labels map[string]string) bool {
return selector != nil && labels != nil
}
if areSelectorsAndLabelsNotNil(policy.Spec.Selectors.VirtualMachineInstanceSelector, vmiLabels) {
matchingVMILabels = countLabelsHelper(policy.Spec.Selectors.VirtualMachineInstanceSelector, vmiLabels)
}
if doesMatch && areSelectorsAndLabelsNotNil(policy.Spec.Selectors.NamespaceSelector, vmiLabels) {
matchingNSLabels = countLabelsHelper(policy.Spec.Selectors.NamespaceSelector, namespaceLabels)
}
if doesMatch {
score = migrationPolicyMatchScore{matchingVMILabels: matchingVMILabels, matchingNSLabels: matchingNSLabels}
}
return doesMatch, score
}