Skip to content

Commit 8434e13

Browse files
authored
[NPM] General translation logic for linux and windows (#1055)
* Create generic translation struct and start using it networkPolicyController * Update ipsm to get more information from NPMCache * Working on translateIngress part and its UTs * Done functions of Translate ingress rule (need to add UTs to test its functions and clean up codes) * Use function for repeated codes * Cleanup UTs * Remove all unused codes in this PR except for ingress rules * Create functions to make codes concise and reorganize ipsets for better readability * Remove duplicated data for targetPod information in every ACL * Move translation logics to /pkg/controlplane/translation dir * Remove redundant codes and resolved some of lint errors * Resolve lint errors and remove unused codes in parseSelector and its UTs * Use unique id for acl policy among network policies and add UTs for port rules * Addresses some comments (will resolve more later) * Complete namespaceSelector UTs and correct some logics for handling namespaceSelector * Use consistent variables and variable for flexibility in UTs * Add more UTs for allowAll and defaultDrop rules and clean-up codes * Remove unused codes * Resolve lint errors * Clean-up and reorganize codes * Revert "Update ipsm to get more information from NPMCache" This reverts commit 477bbaf. * Address comments * Resolve part of lint errors * Add comments for todo things in next PR * Delete unused file and clean-up code * Fix Uts * Remove unnecessary code
1 parent 66453dd commit 8434e13

File tree

16 files changed

+4657
-181
lines changed

16 files changed

+4657
-181
lines changed
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
package translation
2+
3+
import (
4+
"github.com/Azure/azure-container-networking/log"
5+
"github.com/Azure/azure-container-networking/npm/util"
6+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
7+
)
8+
9+
// ParseLabel takes a Azure-NPM processed label then returns if it's referring to complement set,
10+
// and if so, returns the original set as well.
11+
func ParseLabel(label string) (string, bool) {
12+
// The input label is guaranteed to have a non-zero length validated by k8s.
13+
// For label definition, see below parseSelector() function.
14+
if label[0:1] == util.IptablesNotFlag {
15+
return label[1:], true
16+
}
17+
return label, false
18+
}
19+
20+
// GetOperatorAndLabel returns the operator associated with the label and the label without operator.
21+
func GetOperatorAndLabel(labelWithOp string) (op, label string) {
22+
// TODO(jungukcho): check whether this is possible
23+
if labelWithOp == "" {
24+
return op, label
25+
}
26+
27+
// in case "!"" Operaror do not exist
28+
if string(labelWithOp[0]) != util.IptablesNotFlag {
29+
label = labelWithOp
30+
return op, label
31+
}
32+
33+
// in case "!"" Operaror exists
34+
op, label = util.IptablesNotFlag, labelWithOp[1:]
35+
return op, label
36+
}
37+
38+
// GetOperatorsAndLabels returns the operators along with the associated labels.
39+
func GetOperatorsAndLabels(labelsWithOps []string) (ops, labelsWithoutOps []string) {
40+
ops = make([]string, len(labelsWithOps))
41+
labelsWithoutOps = make([]string, len(labelsWithOps))
42+
43+
for i, labelWithOp := range labelsWithOps {
44+
op, labelWithoutOp := GetOperatorAndLabel(labelWithOp)
45+
ops[i] = op
46+
labelsWithoutOps[i] = labelWithoutOp
47+
}
48+
49+
return ops, labelsWithoutOps
50+
}
51+
52+
// getSetNameForMultiValueSelector takes in label with multiple values without operator
53+
// and returns a new 2nd level ipset name
54+
func getSetNameForMultiValueSelector(key string, vals []string) string {
55+
newIPSet := key
56+
for _, val := range vals {
57+
newIPSet = util.GetIpSetFromLabelKV(newIPSet, val)
58+
}
59+
return newIPSet
60+
}
61+
62+
// FlattenNameSpaceSelector will help flatten multiple NameSpace selector match Expressions values
63+
// into multiple label selectors helping with the OR condition.
64+
func FlattenNameSpaceSelector(nsSelector *metav1.LabelSelector) []metav1.LabelSelector {
65+
/*
66+
This function helps to create multiple labelSelectors when given a single multivalue nsSelector
67+
Take below example: this nsSelector has 2 values in a matchSelector.
68+
- namespaceSelector:
69+
matchExpressions:
70+
- key: ns
71+
operator: NotIn
72+
values:
73+
- netpol-x
74+
- netpol-y
75+
76+
goal is to convert this single nsSelector into multiple nsSelectors to preserve OR condition
77+
between multiple values of the matchExpr i.e. this function will return
78+
79+
- namespaceSelector:
80+
matchExpressions:
81+
- key: ns
82+
operator: NotIn
83+
values:
84+
- netpol-x
85+
- namespaceSelector:
86+
matchExpressions:
87+
- key: ns
88+
operator: NotIn
89+
values:
90+
- netpol-y
91+
92+
then, translate policy will replicate each of these nsSelectors to add two different rules in iptables,
93+
resulting in OR condition between the values.
94+
95+
Check TestFlattenNameSpaceSelector 2nd subcase for complex scenario
96+
*/
97+
98+
// To avoid any additional length checks, just return a slice of labelSelectors
99+
// with original nsSelector
100+
if nsSelector == nil {
101+
return []metav1.LabelSelector{}
102+
}
103+
104+
if len(nsSelector.MatchExpressions) == 0 {
105+
return []metav1.LabelSelector{*nsSelector}
106+
}
107+
108+
// create a baseSelector which needs to be same across all
109+
// new labelSelectors
110+
baseSelector := &metav1.LabelSelector{
111+
MatchLabels: nsSelector.MatchLabels,
112+
MatchExpressions: []metav1.LabelSelectorRequirement{},
113+
}
114+
115+
multiValuePresent := false
116+
multiValueMatchExprs := []metav1.LabelSelectorRequirement{}
117+
for _, req := range nsSelector.MatchExpressions {
118+
// Only In and NotIn operators of matchExprs have multiple values
119+
// NPM will ignore single value matchExprs of these operators.
120+
// for multiple values, it will create a slice of them to be used for Zipping with baseSelector
121+
// to create multiple nsSelectors to preserve OR condition across all labels and expressions
122+
switch {
123+
case (req.Operator == metav1.LabelSelectorOpIn) || (req.Operator == metav1.LabelSelectorOpNotIn):
124+
if len(req.Values) == 1 {
125+
// for length 1, add the matchExpr to baseSelector
126+
baseSelector.MatchExpressions = append(baseSelector.MatchExpressions, req)
127+
} else {
128+
multiValuePresent = true
129+
multiValueMatchExprs = append(multiValueMatchExprs, req)
130+
}
131+
case (req.Operator == metav1.LabelSelectorOpExists) || (req.Operator == metav1.LabelSelectorOpDoesNotExist):
132+
// since Exists and NotExists do not contain any values, NPM can safely add them to the baseSelector
133+
baseSelector.MatchExpressions = append(baseSelector.MatchExpressions, req)
134+
default:
135+
log.Errorf("Invalid operator [%s] for selector [%v] requirement", req.Operator, *nsSelector)
136+
}
137+
}
138+
139+
// If there are no multiValue NS selector match expressions
140+
// return the original NsSelector
141+
if !multiValuePresent {
142+
return []metav1.LabelSelector{*nsSelector}
143+
}
144+
145+
// Now use the baseSelector and loop over multiValueMatchExprs to create all
146+
// combinations of values
147+
flatNsSelectors := []metav1.LabelSelector{
148+
*baseSelector.DeepCopy(),
149+
}
150+
for _, req := range multiValueMatchExprs {
151+
flatNsSelectors = zipMatchExprs(flatNsSelectors, req)
152+
}
153+
154+
return flatNsSelectors
155+
}
156+
157+
// zipMatchExprs helps with zipping a given matchExpr with given baseLabelSelectors
158+
// this func will loop over each baseSelector in the slice,
159+
// deepCopies each baseSelector, combines with given matchExpr by looping over each value
160+
// and creating a new LabelSelector with given baseSelector and value matchExpr
161+
// then returns a new slice of these zipped LabelSelectors
162+
func zipMatchExprs(baseSelectors []metav1.LabelSelector, matchExpr metav1.LabelSelectorRequirement) []metav1.LabelSelector {
163+
zippedLabelSelectors := []metav1.LabelSelector{}
164+
for _, selector := range baseSelectors {
165+
for _, value := range matchExpr.Values {
166+
tempBaseSelector := selector.DeepCopy()
167+
tempBaseSelector.MatchExpressions = append(
168+
tempBaseSelector.MatchExpressions,
169+
metav1.LabelSelectorRequirement{
170+
Key: matchExpr.Key,
171+
Operator: matchExpr.Operator,
172+
Values: []string{value},
173+
},
174+
)
175+
zippedLabelSelectors = append(zippedLabelSelectors, *tempBaseSelector)
176+
177+
}
178+
}
179+
return zippedLabelSelectors
180+
}
181+
182+
// parseSelector takes a LabelSelector and returns a slice of processed labels, Lists with members as values.
183+
// this function returns a slice of all the label ipsets excluding multivalue matchExprs
184+
// and a map of labelKeys and labelIpsetname for multivalue match exprs
185+
// higher level functions will need to compute what sets or ipsets should be
186+
// used from this map
187+
func parseSelector(selector *metav1.LabelSelector) (labels []string, vals map[string][]string) {
188+
// TODO(jungukcho): check return values
189+
// labels []string and []string{}
190+
if selector == nil {
191+
return labels, vals
192+
}
193+
194+
labels = []string{}
195+
vals = make(map[string][]string)
196+
if len(selector.MatchLabels) == 0 && len(selector.MatchExpressions) == 0 {
197+
labels = append(labels, "")
198+
return labels, vals
199+
}
200+
201+
sortedKeys, sortedVals := util.SortMap(&selector.MatchLabels)
202+
for i := range sortedKeys {
203+
labels = append(labels, sortedKeys[i]+":"+sortedVals[i])
204+
}
205+
206+
for _, req := range selector.MatchExpressions {
207+
var k string
208+
switch op := req.Operator; op {
209+
case metav1.LabelSelectorOpIn:
210+
k = req.Key
211+
if len(req.Values) == 1 {
212+
labels = append(labels, k+":"+req.Values[0])
213+
} else {
214+
// We are not adding the k:v to labels for multiple values, because, labels are used
215+
// to construct partial IptEntries and if these below labels are added then we are inducing
216+
// AND condition on values of a match expression instead of OR
217+
vals[k] = append(vals[k], req.Values...)
218+
}
219+
case metav1.LabelSelectorOpNotIn:
220+
k = util.IptablesNotFlag + req.Key
221+
if len(req.Values) == 1 {
222+
labels = append(labels, k+":"+req.Values[0])
223+
} else {
224+
vals[k] = append(vals[k], req.Values...)
225+
}
226+
// Exists matches pods with req.Key as key
227+
case metav1.LabelSelectorOpExists:
228+
k = req.Key
229+
labels = append(labels, k)
230+
// DoesNotExist matches pods without req.Key as key
231+
case metav1.LabelSelectorOpDoesNotExist:
232+
k = util.IptablesNotFlag + req.Key
233+
labels = append(labels, k)
234+
default:
235+
log.Errorf("Invalid operator [%s] for selector [%v] requirement", op, *selector)
236+
}
237+
}
238+
239+
return labels, vals
240+
}

0 commit comments

Comments
 (0)