Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
08dc844
basic scenario and investigation
vakalapa Mar 3, 2021
ad6a2dd
adding some basic imcrements to yaml
vakalapa Apr 30, 2021
6668117
First pass at adding 2nd level ipsets for multi value selector expres…
vakalapa Apr 30, 2021
ac135bf
resolving some references for drop match fields
vakalapa Apr 30, 2021
08916ba
Adding some fixes around nil checks
vakalapa May 3, 2021
e031817
ignoring 1 value expr to be added to list mems
vakalapa May 4, 2021
8180aae
not adding lists to the setMap
vakalapa May 4, 2021
6c7ae92
correcting some UTs
vakalapa May 4, 2021
5299dc5
Correcting UTs.
vakalapa May 4, 2021
bf83fd4
fixing all UTs
vakalapa May 5, 2021
928d281
Adding and cleaning some comments
vakalapa May 5, 2021
0553248
basic scenario and investigation
vakalapa Mar 3, 2021
661bbb4
adding some basic imcrements to yaml
vakalapa Apr 30, 2021
7fc6419
First pass at adding 2nd level ipsets for multi value selector expres…
vakalapa Apr 30, 2021
b8cd4b1
resolving some references for drop match fields
vakalapa Apr 30, 2021
4f7a8aa
Adding some fixes around nil checks
vakalapa May 3, 2021
d5cd6c3
ignoring 1 value expr to be added to list mems
vakalapa May 4, 2021
96bc1b7
not adding lists to the setMap
vakalapa May 4, 2021
e066429
correcting some UTs
vakalapa May 4, 2021
9c8af4c
Correcting UTs.
vakalapa May 4, 2021
1227743
fixing all UTs
vakalapa May 5, 2021
fbb0bed
Adding and cleaning some comments
vakalapa May 5, 2021
28f3166
Merge branch 'vakr/k8smultiparse' of https://github.com/Azure/azure-c…
vakalapa May 5, 2021
6c6543b
fixing ns- prefixes for some lists and adding sort for comment
vakalapa May 5, 2021
9aabfe6
fixing nsSlectors, flattening nsSelectors, since list of lists is not…
vakalapa May 6, 2021
51c0a62
correcting some UTs for nsselectors
vakalapa May 6, 2021
8a0b4a5
putting back removed testcase to resolve conflicts
vakalapa May 6, 2021
8b383d6
addressing some comments
vakalapa May 19, 2021
a6e7867
Addressing some comments regarding policy translation
vakalapa May 25, 2021
b171cbb
Merge branch 'master' into vakr/k8smultiparse
vakalapa Jul 1, 2021
cca6c89
fixing some test after the merge
vakalapa Jul 1, 2021
1d5164d
addressing a potential nil deference issue
vakalapa Jul 2, 2021
9dbbace
adding a test case for some corner cases in flattenNS
vakalapa Jul 2, 2021
dfd4b49
adding a TODO to clean up stale code if not used in next iteration
vakalapa Jul 2, 2021
6194ff3
Merge branch 'master' into vakr/k8smultiparse
vakalapa Jul 6, 2021
16fc03a
Fixing some golint
vakalapa Jul 6, 2021
8dda7ae
Fixing a sed in github actions
vakalapa Jul 7, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/cyclonus-netpol-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:

- name: Install Azure NPM
run: |
sed -i 's/mcr.microsoft.com\/containernetworking\/azure-npm:v1.3.1/acnpublic.azurecr.io\/azure-npm:cyclonus/' ./npm/azure-npm.yaml
sed -i 's/mcr.microsoft.com\/containernetworking\/azure-npm:v1.4.1/acnpublic.azurecr.io\/azure-npm:cyclonus/' ./npm/azure-npm.yaml
kind load docker-image acnpublic.azurecr.io/azure-npm:cyclonus --name npm-kind
kubectl apply -f ./npm/azure-npm.yaml

Expand Down
43 changes: 41 additions & 2 deletions npm/ipsm/ipsm.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ import (
utilexec "k8s.io/utils/exec"
)

// ReferCountOperation is used to indicate whether ipset refer count should be increased or decreased.
type ReferCountOperation bool

const (
IncrementOp ReferCountOperation = true
DecrementOp ReferCountOperation = false
)

type ipsEntry struct {
operationFlag string
name string
Expand All @@ -38,11 +46,20 @@ type Ipset struct {
referCount int
}

func (ipset *Ipset) incReferCount() {
ipset.referCount++
}

func (ipset *Ipset) decReferCount() {
ipset.referCount--
}

// NewIpset creates a new instance for Ipset object.
func NewIpset(setName string) *Ipset {
return &Ipset{
name: setName,
elements: make(map[string]string),
name: setName,
elements: make(map[string]string),
referCount: 0,
}
}

Expand Down Expand Up @@ -73,6 +90,21 @@ func (ipsMgr *IpsetManager) Exists(listName string, setName string, kind string)
return true
}

// IpSetReferIncOrDec checks if an element exists in setMap/listMap and then increases or decreases this referCount.
func (ipsMgr *IpsetManager) IpSetReferIncOrDec(ipsetName string, kind string, countOperation ReferCountOperation) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is better not to expose this function to other packages. IpSetReferIncOrDec() -> ipSetReferIncOrDec.

May Improve readability by making separate functions for increasing and decreasing reference count instead of using flag if possible, but, do not have strong opinion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will need to expose this to network policy controller as netpol is the one referencing out ipsetmaps

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have plan to extend this function for other types in addition to "list" as well?
In this PR, kind is always "list".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes in the next work item(refer coutn) we will extend support of this IncDec to SetMap where we will track the use of iptable rules and their references to a given ipset. we will track both kinds of ipsets.

m := ipsMgr.SetMap
if kind == util.IpsetSetListFlag {
m = ipsMgr.ListMap
}

switch countOperation {
case IncrementOp:
m[ipsetName].incReferCount()
case DecrementOp:
m[ipsetName].decReferCount()
}
}

// SetExists checks if an ipset exists, and returns the type
func (ipsMgr *IpsetManager) SetExists(setName string) (bool, string) {
_, exists := ipsMgr.SetMap[setName]
Expand Down Expand Up @@ -122,6 +154,11 @@ func (ipsMgr *IpsetManager) DeleteList(listName string) error {
set: util.GetHashedName(listName),
}

if ipsMgr.ListMap[listName].referCount > 0 {
ipsMgr.IpSetReferIncOrDec(listName, util.IpsetSetListFlag, DecrementOp)
return nil
}

if errCode, err := ipsMgr.Run(entry); err != nil {
if errCode == 1 {
return nil
Expand Down Expand Up @@ -428,6 +465,8 @@ func (ipsMgr *IpsetManager) DeleteFromSet(setName, ip, podKey string) error {
return nil
}

// TODO this below function is to be extended while improving ipset refer count
// support, if not used, please remove this stale function.
// Clean removes all the empty sets & lists under the namespace.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When do we use this function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean() function is not being currently, this is for future work items.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest not to add function which is not used in this PR. I think this is a good practice.
There are multiple reasons.

  1. Reviewer do not need to spend time.
  2. Add the function with the PR for your future work items. So, it will be well aligned and easy to understand PR.
  3. There are possibility that this function will not be used in the future. At that time, this function just stays for long time.

I think you have future work item to use this function. So, add this function in the PR.

func (ipsMgr *IpsetManager) Clean() error {
for setName, set := range ipsMgr.SetMap {
Expand Down
33 changes: 29 additions & 4 deletions npm/networkPolicyController.go
Original file line number Diff line number Diff line change
Expand Up @@ -351,10 +351,24 @@ func (c *networkPolicyController) syncAddAndUpdateNetPol(netPolObj *networkingv1
return fmt.Errorf("[syncAddAndUpdateNetPol] Error: creating ipset named port %s with err: %v", set, err)
}
}
for _, list := range lists {
if err = ipsMgr.CreateList(list); err != nil {
return fmt.Errorf("[syncAddAndUpdateNetPol] Error: creating ipset list %s with err: %v", list, err)

// lists is a map with list name and members as value
// NPM will create the list first and increments the refer count
for listKey := range lists {
if err = ipsMgr.CreateList(listKey); err != nil {
return fmt.Errorf("[syncAddAndUpdateNetPol] Error: creating ipset list %s with err: %v", listKey, err)
}
ipsMgr.IpSetReferIncOrDec(listKey, util.IpsetSetListFlag, ipsm.IncrementOp)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better to include this call in CreateList(). So, we do not need to expose this function to other packages and encapsulate information in ipsMgr.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As i explained above, this should not be used in createList, but only when netowrkPolicy or a 2nd level List uses an IPset

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case, I prefer to having another function which functions "CreateList and IPSetReferIncOrDec". It will be easy to remember and help to encapsulate details.
I assume that your next PR is about those reference counts, we can revisit this in the PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, in refer count work item, we can look at different scenarios and better organize these functions.

}
// Then NPM will add members to the above list, this is to avoid members being added
// to lists before they are created.
for listKey, listLabelsMembers := range lists {
for _, listMember := range listLabelsMembers {
if err = ipsMgr.AddToList(listKey, listMember); err != nil {
return fmt.Errorf("[syncAddAndUpdateNetPol] Error: Adding ipset member %s to ipset list %s with err: %v", listMember, listKey, err)
}
}
ipsMgr.IpSetReferIncOrDec(listKey, util.IpsetSetListFlag, ipsm.IncrementOp)
Copy link
Contributor

@JungukCho JungukCho May 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better to include this call in AddToList(). So, we do not need to expose this functions to other packages and encapsulate information in ipsMgr.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I am not incrementing or decrementing a List count on creation is that, unless a network policy uses a IPSet, it is not considered be referred by others. So i chose to expose the IpSetReferIncOrDec to NetPol package, to increment the refer only when a NetworkPolicy uses this IPset, same when we delete a network policy is when we decrement the value

}

if err = c.createCidrsRule("in", netPolObj.ObjectMeta.Name, netPolObj.ObjectMeta.Namespace, ingressIPCidrs, ipsMgr); err != nil {
Expand Down Expand Up @@ -385,7 +399,7 @@ func (c *networkPolicyController) cleanUpNetworkPolicy(netPolKey string, isSafeC
ipsMgr := c.npMgr.NsMap[util.KubeAllNamespacesFlag].IpsMgr
iptMgr := c.npMgr.NsMap[util.KubeAllNamespacesFlag].iptMgr
// translate policy from "cachedNetPolObj"
_, _, _, ingressIPCidrs, egressIPCidrs, iptEntries := translatePolicy(cachedNetPolObj)
_, _, lists, ingressIPCidrs, egressIPCidrs, iptEntries := translatePolicy(cachedNetPolObj)

var err error
// delete iptables entries
Expand All @@ -395,6 +409,17 @@ func (c *networkPolicyController) cleanUpNetworkPolicy(netPolKey string, isSafeC
}
}

// lists is a map with list name and members as value
for listKey := range lists {
// We do not have delete the members before deleting set as,
// 1. ipset allows deleting a ipset list with members
// 2. if the refer count is more than one we should not remove members
// 3. for reduced datapath operations
if err = ipsMgr.DeleteList(listKey); err != nil {
return fmt.Errorf("[syncAddAndUpdateNetPol] Error: creating ipset list %s with err: %v", listKey, err)
}
}

// delete ipset list related to ingress CIDRs
if err = c.removeCidrsRule("in", cachedNetPolObj.Name, cachedNetPolObj.Namespace, ingressIPCidrs, ipsMgr); err != nil {
return fmt.Errorf("[cleanUpNetworkPolicy] Error: removeCidrsRule in due to %v", err)
Expand Down
2 changes: 1 addition & 1 deletion npm/npm.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ func (npMgr *NetworkPolicyManager) backup() {
time.Sleep(backupWaitTimeInSeconds * time.Second)

if err = iptMgr.Save(util.IptablesConfigFile); err != nil {
metrics.SendErrorLogAndMetric(util.NpmID, "Error: failed to back up Azure-NPM states")
metrics.SendErrorLogAndMetric(util.NpmID, "Error: failed to back up Azure-NPM states %s", err.Error())
}
}
}
Expand Down
180 changes: 154 additions & 26 deletions npm/parseSelector.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,73 +121,201 @@ func sortSelector(selector *metav1.LabelSelector) {
selector.MatchExpressions = sortedReqs
}

// getSetNameForMultiValueSelector takes in label with multiple values without operator
// and returns a new 2nd level ipset name
func getSetNameForMultiValueSelector(key string, vals []string) string {
newIpSet := key
for _, val := range vals {
newIpSet = util.GetIpSetFromLabelKV(newIpSet, val)
}
return newIpSet
}

// HashSelector returns the hash value of the selector.
func HashSelector(selector *metav1.LabelSelector) string {
sortSelector(selector)
return util.Hash(fmt.Sprintf("%v", selector))
}

// parseSelector takes a LabelSelector and returns a slice of processed labels, keys and values.
func parseSelector(selector *metav1.LabelSelector) ([]string, []string, []string) {
// flattenNameSpaceSelector will help flatten multiple NameSpace selector match Expressions values
// into multiple label selectors helping with the OR condition.
func FlattenNameSpaceSelector(nsSelector *metav1.LabelSelector) []metav1.LabelSelector {
/*
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure we need these long comments. I think it was well explained in comments (140-142) and is better to add concise explanation in the comments (140-142) based on summary of these long comments.
For example, matchExpressions "ns in (netpol-x, netpol-y)" is converted to multiple matchExpressions [
"ns in (netpol-x)", "ns in (netpol-y)"], then translate policy will replicate each of these nsSelectors to add two different rules in iptables, resulting in OR condition between the values.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comes back to what we discussed in our meeting today, having a visual representation of what the YAML looks like and what the transformation looks like will help other developers to quickly understand. I would like to keep this as is if its ok.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be clear, I suggested putting comprehensive information (e.g., pods yaml and netpol yaml which shows the problem in our NPM) to reproduce the problem, which helps to understand big picture at the first. T
his code is very straightforward. So, I don't think we need this, but if you want to keep it, I don't have strong opinion.

This function helps to create multiple labelSelectors when given a single multivalue nsSelector
Take below exmaple: this nsSelector has 2 values in a matchSelector.
- namespaceSelector:
matchExpressions:
- key: ns
operator: NotIn
values:
- netpol-x
- netpol-y

goal is to convert this single nsSelector into multiple nsSelectors to preserve OR condition
between multiple values of the matchExpr i.e. this function will return

- namespaceSelector:
matchExpressions:
- key: ns
operator: NotIn
values:
- netpol-x
- namespaceSelector:
matchExpressions:
- key: ns
operator: NotIn
values:
- netpol-y

then, translate policy will replicate each of these nsSelectors to add two different rules in iptables,
resulting in OR condition between the values.

Check TestFlattenNameSpaceSelector 2nd subcase for complex scenario
*/

// To avoid any additional length checks, just return a slice of labelSelectors
// with original nsSelector
if nsSelector == nil {
return []metav1.LabelSelector{}
}

if len(nsSelector.MatchExpressions) == 0 {
return []metav1.LabelSelector{*nsSelector}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it different to just return nsSelector?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See previous comment.

}

// create a baseSelector which needs to be same across all
// new labelSelectors
baseSelector := &metav1.LabelSelector{
MatchLabels: nsSelector.MatchLabels,
MatchExpressions: []metav1.LabelSelectorRequirement{},
}

multiValuePresent := false
multiValueMatchExprs := []metav1.LabelSelectorRequirement{}
for _, req := range nsSelector.MatchExpressions {

// Only In and NotIn operators of matchExprs have multiple values
// NPM will ignore single value matchExprs of these operators.
// for multiple values, it will create a slice of them to be used for Zipping with baseSelector
// to create multiple nsSelectors to preserve OR condition across all labels and expressions
if (req.Operator == metav1.LabelSelectorOpIn) || (req.Operator == metav1.LabelSelectorOpNotIn) {
if len(req.Values) == 1 {
// for length 1, add the matchExpr to baseSelector
baseSelector.MatchExpressions = append(baseSelector.MatchExpressions, req)
} else {
multiValuePresent = true
multiValueMatchExprs = append(multiValueMatchExprs, req)
}
} else if (req.Operator == metav1.LabelSelectorOpExists) || (req.Operator == metav1.LabelSelectorOpDoesNotExist) {
// since Exists and NotExists do not contain any values, NPM can safely add them to the baseSelector
baseSelector.MatchExpressions = append(baseSelector.MatchExpressions, req)
} else {
log.Errorf("Invalid operator [%s] for selector [%v] requirement", req.Operator, *nsSelector)
}
}

// If there are no multiValue NS selector match expressions
// return the original NsSelector
if !multiValuePresent {
return []metav1.LabelSelector{*nsSelector}
}

// Now use the baseSelector and loop over multiValueMatchExprs to create all
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Highly suggest moving codes from 185-192 to "zipMatchExprs". It will improve readability. Now, I need to track for loop in 190 with zipMatchExprs function which uses returned values as input again.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or moving codes from "zipMatchExprs" to here. The function name of "zipMatchExprs" is not very intuitive at least to me. I think "FlattenNameSpaceSelector" function has a good name and combining all codes in the function is good to understand and not deep.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I modelled it as a recursive function, where we take in a slice and expand it using each expr.

// combinations of values
flatNsSelectors := []metav1.LabelSelector{
*baseSelector.DeepCopy(),
}
for _, req := range multiValueMatchExprs {
flatNsSelectors = zipMatchExprs(flatNsSelectors, req)
}

return flatNsSelectors
}

// zipMatchExprs helps with zipping a given matchExpr with given baseLabelSelectors
// this func will loop over each baseSelector in the slice,
// deepCopies each baseSelector, combines with given matchExpr by looping over each value
// and creating a new LabelSelector with given baseSelector and value matchExpr
// then returns a new slice of these zipped LabelSelectors
func zipMatchExprs(baseSelectors []metav1.LabelSelector, matchExpr metav1.LabelSelectorRequirement) []metav1.LabelSelector {
zippedLabelSelectors := []metav1.LabelSelector{}
for _, selector := range baseSelectors {
for _, value := range matchExpr.Values {
tempBaseSelector := selector.DeepCopy()
tempBaseSelector.MatchExpressions = append(
tempBaseSelector.MatchExpressions,
metav1.LabelSelectorRequirement{
Key: matchExpr.Key,
Operator: matchExpr.Operator,
Values: []string{value},
},
)
zippedLabelSelectors = append(zippedLabelSelectors, *tempBaseSelector)

}
}
return zippedLabelSelectors
}

// parseSelector takes a LabelSelector and returns a slice of processed labels, Lists with members as values.
// this function returns a slice of all the label ipsets excluding multivalue matchExprs
// and a map of labelKeys and labelIpsetname for multivalue match exprs
// higher level functions will need to compute what sets or ipsets should be
// used from this map
func parseSelector(selector *metav1.LabelSelector) ([]string, map[string][]string) {
var (
labels []string
keys []string
vals []string
vals map[string][]string
)

vals = make(map[string][]string)
if selector == nil {
return labels, keys, vals
return labels, vals
}

if len(selector.MatchLabels) == 0 && len(selector.MatchExpressions) == 0 {
labels = append(labels, "")
keys = append(keys, "")
vals = append(vals, "")

return labels, keys, vals
return labels, vals
}

sortedKeys, sortedVals := util.SortMap(&selector.MatchLabels)

for i := range sortedKeys {
labels = append(labels, sortedKeys[i]+":"+sortedVals[i])
}
keys = append(keys, sortedKeys...)
vals = append(vals, sortedVals...)

for _, req := range selector.MatchExpressions {
var k string
switch op := req.Operator; op {
case metav1.LabelSelectorOpIn:
for _, v := range req.Values {
k = req.Key
keys = append(keys, k)
vals = append(vals, v)
labels = append(labels, k+":"+v)
k = req.Key
if len(req.Values) == 1 {
labels = append(labels, k+":"+req.Values[0])
} else {
// We are not adding the k:v to labels for multiple values, because, labels are used
// to contruct partial IptEntries and if these below labels are added then we are inducing
// AND condition on values of a match expression instead of OR
vals[k] = append(vals[k], req.Values...)
}
case metav1.LabelSelectorOpNotIn:
for _, v := range req.Values {
k = util.IptablesNotFlag + req.Key
keys = append(keys, k)
vals = append(vals, v)
labels = append(labels, k+":"+v)
k = util.IptablesNotFlag + req.Key
if len(req.Values) == 1 {
labels = append(labels, k+":"+req.Values[0])
} else {
vals[k] = append(vals[k], req.Values...)
}
// Exists matches pods with req.Key as key
case metav1.LabelSelectorOpExists:
k = req.Key
keys = append(keys, req.Key)
vals = append(vals, "")
labels = append(labels, k)
// DoesNotExist matches pods without req.Key as key
case metav1.LabelSelectorOpDoesNotExist:
k = util.IptablesNotFlag + req.Key
keys = append(keys, k)
vals = append(vals, "")
labels = append(labels, k)
default:
log.Errorf("Invalid operator [%s] for selector [%v] requirement", op, *selector)
}
}

return labels, keys, vals
return labels, vals
}
Loading