From 6c6f020f582a2be2ab2528ce0a370296da607645 Mon Sep 17 00:00:00 2001 From: Dyanngg Date: Mon, 7 Nov 2022 16:55:45 -0800 Subject: [PATCH] Support SameLabels as peer Namespace selection in ACNP Signed-off-by: Dyanngg --- .../antrea/crds/clusternetworkpolicy.yaml | 8 + build/yamls/antrea-aks.yml | 8 + build/yamls/antrea-crds.yml | 8 + build/yamls/antrea-eks.yml | 8 + build/yamls/antrea-gke.yml | 8 + build/yamls/antrea-ipsec.yml | 8 + build/yamls/antrea.yml | 8 + pkg/apis/crd/v1alpha1/types.go | 6 + .../crd/v1alpha1/zz_generated.deepcopy.go | 7 +- .../networkpolicy/clusternetworkpolicy.go | 175 +++++++++++++++--- .../clusternetworkpolicy_test.go | 168 ++++++++++++++++- 11 files changed, 377 insertions(+), 35 deletions(-) diff --git a/build/charts/antrea/crds/clusternetworkpolicy.yaml b/build/charts/antrea/crds/clusternetworkpolicy.yaml index 4402a3a4548..c237152193c 100644 --- a/build/charts/antrea/crds/clusternetworkpolicy.yaml +++ b/build/charts/antrea/crds/clusternetworkpolicy.yaml @@ -334,6 +334,10 @@ spec: enum: - Self type: string + sameLabels: + type: array + items: + type: string ipBlock: type: object properties: @@ -582,6 +586,10 @@ spec: enum: - Self type: string + sameLabels: + type: array + items: + type: string ipBlock: type: object properties: diff --git a/build/yamls/antrea-aks.yml b/build/yamls/antrea-aks.yml index a4daa83dada..3efd1a23c9e 100644 --- a/build/yamls/antrea-aks.yml +++ b/build/yamls/antrea-aks.yml @@ -712,6 +712,10 @@ spec: enum: - Self type: string + sameLabels: + type: array + items: + type: string ipBlock: type: object properties: @@ -960,6 +964,10 @@ spec: enum: - Self type: string + sameLabels: + type: array + items: + type: string ipBlock: type: object properties: diff --git a/build/yamls/antrea-crds.yml b/build/yamls/antrea-crds.yml index 6cf3a870d2c..ac7fec35c47 100644 --- a/build/yamls/antrea-crds.yml +++ b/build/yamls/antrea-crds.yml @@ -705,6 +705,10 @@ spec: enum: - Self type: string + sameLabels: + type: array + items: + type: string ipBlock: type: object properties: @@ -953,6 +957,10 @@ spec: enum: - Self type: string + sameLabels: + type: array + items: + type: string ipBlock: type: object properties: diff --git a/build/yamls/antrea-eks.yml b/build/yamls/antrea-eks.yml index 9d66dee121e..b0ecd698bf6 100644 --- a/build/yamls/antrea-eks.yml +++ b/build/yamls/antrea-eks.yml @@ -712,6 +712,10 @@ spec: enum: - Self type: string + sameLabels: + type: array + items: + type: string ipBlock: type: object properties: @@ -960,6 +964,10 @@ spec: enum: - Self type: string + sameLabels: + type: array + items: + type: string ipBlock: type: object properties: diff --git a/build/yamls/antrea-gke.yml b/build/yamls/antrea-gke.yml index 945c128c87a..9b8dec74636 100644 --- a/build/yamls/antrea-gke.yml +++ b/build/yamls/antrea-gke.yml @@ -712,6 +712,10 @@ spec: enum: - Self type: string + sameLabels: + type: array + items: + type: string ipBlock: type: object properties: @@ -960,6 +964,10 @@ spec: enum: - Self type: string + sameLabels: + type: array + items: + type: string ipBlock: type: object properties: diff --git a/build/yamls/antrea-ipsec.yml b/build/yamls/antrea-ipsec.yml index 67e79a066c1..c4281655379 100644 --- a/build/yamls/antrea-ipsec.yml +++ b/build/yamls/antrea-ipsec.yml @@ -712,6 +712,10 @@ spec: enum: - Self type: string + sameLabels: + type: array + items: + type: string ipBlock: type: object properties: @@ -960,6 +964,10 @@ spec: enum: - Self type: string + sameLabels: + type: array + items: + type: string ipBlock: type: object properties: diff --git a/build/yamls/antrea.yml b/build/yamls/antrea.yml index 0d4ad7c43ba..c17d27002e2 100644 --- a/build/yamls/antrea.yml +++ b/build/yamls/antrea.yml @@ -712,6 +712,10 @@ spec: enum: - Self type: string + sameLabels: + type: array + items: + type: string ipBlock: type: object properties: @@ -960,6 +964,10 @@ spec: enum: - Self type: string + sameLabels: + type: array + items: + type: string ipBlock: type: object properties: diff --git a/pkg/apis/crd/v1alpha1/types.go b/pkg/apis/crd/v1alpha1/types.go index e21af92ed98..f7dbb676264 100644 --- a/pkg/apis/crd/v1alpha1/types.go +++ b/pkg/apis/crd/v1alpha1/types.go @@ -542,8 +542,14 @@ type AppliedTo struct { Service *NamespacedName `json:"service,omitempty"` } +// PeerNamespaces describes criteria for selecting Pod/ExternalEntity +// from matched Namespaces. Only one of the criteria can be set. type PeerNamespaces struct { + // Selects from the same Namespace of the appliedTo workloads. Match NamespaceMatchType `json:"match,omitempty"` + // Selects Namespaces that share the same values for the given set of label keys + // with the appliedTo Namespace. Namespaces must have all the label keys. + SameLabels []string `json:"sameLabels,omitempty"` } // NamespaceMatchType describes Namespace matching strategy. diff --git a/pkg/apis/crd/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/crd/v1alpha1/zz_generated.deepcopy.go index 064ade764eb..a7fe58bb16a 100644 --- a/pkg/apis/crd/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/crd/v1alpha1/zz_generated.deepcopy.go @@ -642,7 +642,7 @@ func (in *NetworkPolicyPeer) DeepCopyInto(out *NetworkPolicyPeer) { if in.Namespaces != nil { in, out := &in.Namespaces, &out.Namespaces *out = new(PeerNamespaces) - **out = **in + (*in).DeepCopyInto(*out) } if in.ExternalEntitySelector != nil { in, out := &in.ExternalEntitySelector, &out.ExternalEntitySelector @@ -852,6 +852,11 @@ func (in *Packet) DeepCopy() *Packet { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PeerNamespaces) DeepCopyInto(out *PeerNamespaces) { *out = *in + if in.SameLabels != nil { + in, out := &in.SameLabels, &out.SameLabels + *out = make([]string, len(*in)) + copy(*out, *in) + } return } diff --git a/pkg/controller/networkpolicy/clusternetworkpolicy.go b/pkg/controller/networkpolicy/clusternetworkpolicy.go index 98984e91db5..a058831a267 100644 --- a/pkg/controller/networkpolicy/clusternetworkpolicy.go +++ b/pkg/controller/networkpolicy/clusternetworkpolicy.go @@ -16,6 +16,7 @@ package networkpolicy import ( "reflect" + "strings" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -33,6 +34,11 @@ import ( utilsets "antrea.io/antrea/pkg/util/sets" ) +const ( + labelValueUndefined = "Undefined" + labelValueSeparater = "," +) + func getACNPReference(cnp *crdv1alpha1.ClusterNetworkPolicy) *controlplane.NetworkPolicyReference { return &controlplane.NetworkPolicyReference{ Type: controlplane.AntreaClusterNetworkPolicy, @@ -336,23 +342,23 @@ func (n *NetworkPolicyController) processClusterNetworkPolicy(cnp *crdv1alpha1.C addressGroups := map[string]*antreatypes.AddressGroup{} // If appliedTo is set at spec level and the ACNP has per-namespace rules, then each appliedTo needs // to be split into appliedToGroups for each of its affected Namespace. - var clusterAppliedToAffectedNS []string - // atgForNamespace is the appliedToGroups split by Namespaces. - var atgForNamespace []*antreatypes.AppliedToGroup + atgPerAffectedNS := map[string]*antreatypes.AppliedToGroup{} + // When appliedTo is set at spec level and the ACNP has rules that select peer Namespaces by sameLabels, + // this field tracks the labels of all Namespaces selected by the appliedTo. + affectedNSAndLabels := map[string]map[string]string{} if hasPerNamespaceRule && len(cnp.Spec.AppliedTo) > 0 { for _, at := range cnp.Spec.AppliedTo { if at.ServiceAccount != nil { atg := n.createAppliedToGroup(at.ServiceAccount.Namespace, serviceAccountNameToPodSelector(at.ServiceAccount.Name), nil, nil) appliedToGroups = mergeAppliedToGroups(appliedToGroups, atg) - clusterAppliedToAffectedNS = append(clusterAppliedToAffectedNS, at.ServiceAccount.Namespace) - atgForNamespace = append(atgForNamespace, atg) + atgPerAffectedNS[at.ServiceAccount.Namespace] = atg + affectedNSAndLabels[at.ServiceAccount.Namespace] = n.getNamespaceLabels(at.ServiceAccount.Namespace) } else { - affectedNS := n.getAffectedNamespacesForAppliedTo(at) - for _, ns := range affectedNS { + affectedNSAndLabels = n.getAffectedNamespacesForAppliedTo(at) + for ns := range affectedNSAndLabels { atg := n.createAppliedToGroup(ns, at.PodSelector, nil, at.ExternalEntitySelector) appliedToGroups = mergeAppliedToGroups(appliedToGroups, atg) - clusterAppliedToAffectedNS = append(clusterAppliedToAffectedNS, ns) - atgForNamespace = append(atgForNamespace, atg) + atgPerAffectedNS[ns] = atg } } } @@ -361,7 +367,7 @@ func (n *NetworkPolicyController) processClusterNetworkPolicy(cnp *crdv1alpha1.C processRules := func(cnpRules []crdv1alpha1.Rule, direction controlplane.Direction) { for idx, cnpRule := range cnpRules { services, namedPortExists := toAntreaServicesForCRD(cnpRule.Ports, cnpRule.Protocols) - clusterPeers, perNSPeers := splitPeersByScope(cnpRule, direction) + clusterPeers, perNSPeers, nsLabelPeers := splitPeersByScope(cnpRule, direction) addRule := func(peer *controlplane.NetworkPolicyPeer, ruleAddressGroups []*antreatypes.AddressGroup, dir controlplane.Direction, ruleAppliedTos []*antreatypes.AppliedToGroup) { rule := controlplane.NetworkPolicyRule{ Direction: dir, @@ -384,7 +390,7 @@ func (n *NetworkPolicyController) processClusterNetworkPolicy(cnp *crdv1alpha1.C } // When a rule's NetworkPolicyPeer is empty, a cluster level rule should be created // with an Antrea peer matching all addresses. - if len(clusterPeers) > 0 || len(perNSPeers) == 0 { + if len(clusterPeers) > 0 || len(perNSPeers)+len(nsLabelPeers) == 0 { ruleAppliedTos := cnpRule.AppliedTo // For ACNPs that have per-namespace rules, cluster-level rules will be created with appliedTo // set as the spec appliedTo for each rule. @@ -403,10 +409,10 @@ func (n *NetworkPolicyController) processClusterNetworkPolicy(cnp *crdv1alpha1.C if len(perNSPeers) > 0 { if len(cnp.Spec.AppliedTo) > 0 { // Create a rule for each affected Namespace of appliedTo at spec level - for i := range clusterAppliedToAffectedNS { - klog.V(4).Infof("Adding a new per-namespace rule with appliedTo %v for rule %d of %s", clusterAppliedToAffectedNS[i], idx, cnp.Name) - peer, ags := n.toNamespacedPeerForCRD(perNSPeers, clusterAppliedToAffectedNS[i]) - addRule(peer, ags, direction, []*antreatypes.AppliedToGroup{atgForNamespace[i]}) + for ns, atg := range atgPerAffectedNS { + klog.V(4).Infof("Adding a new per-namespace rule with appliedTo %v for rule %d of %s", atg, idx, cnp.Name) + peer, ags := n.toNamespacedPeerForCRD(perNSPeers, ns) + addRule(peer, ags, direction, []*antreatypes.AppliedToGroup{atg}) } } else { // Create a rule for each affected Namespace of appliedTo at rule level @@ -418,7 +424,7 @@ func (n *NetworkPolicyController) processClusterNetworkPolicy(cnp *crdv1alpha1.C addRule(peer, ags, direction, []*antreatypes.AppliedToGroup{atg}) } else { affectedNS := n.getAffectedNamespacesForAppliedTo(at) - for _, ns := range affectedNS { + for ns := range affectedNS { atg := n.createAppliedToGroup(ns, at.PodSelector, nil, at.ExternalEntitySelector) klog.V(4).Infof("Adding a new per-namespace rule with appliedTo %v for rule %d of %s", atg, idx, cnp.Name) peer, ags := n.toNamespacedPeerForCRD(perNSPeers, ns) @@ -428,6 +434,41 @@ func (n *NetworkPolicyController) processClusterNetworkPolicy(cnp *crdv1alpha1.C } } } + if len(nsLabelPeers) > 0 { + if len(cnp.Spec.AppliedTo) > 0 { + // All affected Namespaces and their labels are already stored in affectedNSAndLabels + for _, peer := range nsLabelPeers { + nsGroupByLabelVal := groupNamespacesByLabelValue(affectedNSAndLabels, peer.Namespaces.SameLabels) + for labelValues, groupedNamespaces := range nsGroupByLabelVal { + peer, atgs, ags := n.toAntreaPeerForSameLabelNamespaces(peer, atgPerAffectedNS, labelValues, groupedNamespaces) + addRule(peer, ags, direction, atgs) + } + } + } else { + atgPerRuleAffectedNS := map[string]*antreatypes.AppliedToGroup{} + ruleAffectedNSLabels := map[string]map[string]string{} + for _, at := range cnpRule.AppliedTo { + if at.ServiceAccount != nil { + atg := n.createAppliedToGroup(at.ServiceAccount.Namespace, serviceAccountNameToPodSelector(at.ServiceAccount.Name), nil, nil) + atgPerRuleAffectedNS[at.ServiceAccount.Namespace] = atg + ruleAffectedNSLabels[at.ServiceAccount.Namespace] = n.getNamespaceLabels(at.ServiceAccount.Namespace) + } else { + ruleAffectedNSLabels = n.getAffectedNamespacesForAppliedTo(at) + for ns := range ruleAffectedNSLabels { + atg := n.createAppliedToGroup(ns, at.PodSelector, nil, at.ExternalEntitySelector) + atgPerRuleAffectedNS[ns] = atg + } + } + } + for _, peer := range nsLabelPeers { + nsGroupByLabelVal := groupNamespacesByLabelValue(ruleAffectedNSLabels, peer.Namespaces.SameLabels) + for labelValues, groupedNamespaces := range nsGroupByLabelVal { + peer, atgs, ags := n.toAntreaPeerForSameLabelNamespaces(peer, atgPerRuleAffectedNS, labelValues, groupedNamespaces) + addRule(peer, ags, direction, atgs) + } + } + } + } } } // Compute NetworkPolicyRules for Ingress Rules. @@ -469,14 +510,14 @@ func serviceAccountNameToPodSelector(saName string) *metav1.LabelSelector { func hasPerNamespaceRule(cnp *crdv1alpha1.ClusterNetworkPolicy) bool { for _, ingress := range cnp.Spec.Ingress { for _, peer := range ingress.From { - if peer.Namespaces != nil && peer.Namespaces.Match == crdv1alpha1.NamespaceMatchSelf { + if peer.Namespaces != nil { return true } } } for _, egress := range cnp.Spec.Egress { for _, peer := range egress.To { - if peer.Namespaces != nil && peer.Namespaces.Match == crdv1alpha1.NamespaceMatchSelf { + if peer.Namespaces != nil { return true } } @@ -484,6 +525,78 @@ func hasPerNamespaceRule(cnp *crdv1alpha1.ClusterNetworkPolicy) bool { return false } +func (n *NetworkPolicyController) getNamespaceLabels(ns string) map[string]string { + namespace, _ := n.namespaceLister.Get(ns) + return namespace.Labels +} + +// groupNamespaceByLabelValue groups Namespaces if they have the same label value for all the +// label keys listed. If a Namespace is missing at least one of the label keys, it will be +// not be grouped. Example: +// ns1: app=web, tier=test, tenant=t1 +// ns2: app=web, tier=test, tenant=t2 +// ns3: app=web, tier=production, tenant=t1 +// ns4: app=web, tier=production, tenant=t2 +// ns5: app=db, tenant=t1 +// labelKeys = [app, tier] +// Result after grouping: +// "web,test,": [ns1, ns2] +// "web,production,": [ns3, ns4] +func groupNamespacesByLabelValue(affectedNSAndLabels map[string]map[string]string, labelKeys []string) map[string][]string { + nsGroupedByLabelVal := map[string][]string{} + for ns, nsLabels := range affectedNSAndLabels { + if groupKey := getLabelValues(nsLabels, labelKeys); groupKey != labelValueUndefined { + nsGroupedByLabelVal[groupKey] = append(nsGroupedByLabelVal[groupKey], ns) + } + } + return nsGroupedByLabelVal +} + +func getLabelValues(labels map[string]string, labelKeys []string) string { + key := "" + for _, k := range labelKeys { + if v, ok := labels[k]; !ok { + return labelValueUndefined + } else { + key += v + labelValueSeparater + } + } + return key +} + +// labelKeyValPairsToSelector creates a LabelSelector based on a list of label keys +// and their expected values. +func labelKeyValPairsToSelector(labelKeys []string, labelValues string) *metav1.LabelSelector { + labelValuesSep := strings.Split(labelValues, labelValueSeparater) + labelMatchCriteria := map[string]string{} + for i := range labelKeys { + labelMatchCriteria[labelKeys[i]] = labelValuesSep[i] + } + return &metav1.LabelSelector{ + MatchLabels: labelMatchCriteria, + } +} + +// toAntreaPeerForSameLabelNamespaces computes the appliedToGroups and addressGroups for each +// group of Namespaces who have the same values for the sameLabels keys. +func (n *NetworkPolicyController) toAntreaPeerForSameLabelNamespaces(peer crdv1alpha1.NetworkPolicyPeer, + atgPerAffectedNS map[string]*antreatypes.AppliedToGroup, + labelValues string, + namespacesByLabelValues []string) (*controlplane.NetworkPolicyPeer, []*antreatypes.AppliedToGroup, []*antreatypes.AddressGroup) { + + sameLabelKeys := peer.Namespaces.SameLabels + // select Namespaces who, for specific label keys, have the same values as the appliedTo Namespaces. + nsSelForSameLabels := labelKeyValPairsToSelector(sameLabelKeys, labelValues) + addressGroups := []*antreatypes.AddressGroup{n.createAddressGroup("", peer.PodSelector, nsSelForSameLabels, peer.ExternalEntitySelector, nil)} + antreaPeer := &controlplane.NetworkPolicyPeer{AddressGroups: getAddressGroupNames(addressGroups)} + var atgs []*antreatypes.AppliedToGroup + for _, ns := range namespacesByLabelValues { + atgForNamespace, _ := atgPerAffectedNS[ns] + atgs = append(atgs, atgForNamespace) + } + return antreaPeer, atgs, addressGroups +} + // processClusterAppliedTo processes appliedTo groups in Antrea ClusterNetworkPolicy set // at cluster level (appliedTo groups which will not need to be split by Namespaces). func (n *NetworkPolicyController) processClusterAppliedTo(appliedTo []crdv1alpha1.AppliedTo) []*antreatypes.AppliedToGroup { @@ -508,32 +621,36 @@ func (n *NetworkPolicyController) processClusterAppliedTo(appliedTo []crdv1alpha // splitPeersByScope splits the ClusterNetworkPolicy peers in the rule by whether the peer // is cluster-scoped or per-namespace. -func splitPeersByScope(rule crdv1alpha1.Rule, dir controlplane.Direction) ([]crdv1alpha1.NetworkPolicyPeer, []crdv1alpha1.NetworkPolicyPeer) { - var clusterPeers, perNSPeers []crdv1alpha1.NetworkPolicyPeer +func splitPeersByScope(rule crdv1alpha1.Rule, dir controlplane.Direction) ([]crdv1alpha1.NetworkPolicyPeer, []crdv1alpha1.NetworkPolicyPeer, []crdv1alpha1.NetworkPolicyPeer) { + var clusterPeers, perNSPeers, nsLabelPeers []crdv1alpha1.NetworkPolicyPeer peers := rule.From if dir == controlplane.DirectionOut { peers = rule.To } for _, peer := range peers { - if peer.Namespaces != nil && peer.Namespaces.Match == crdv1alpha1.NamespaceMatchSelf { - perNSPeers = append(perNSPeers, peer) + if peer.Namespaces != nil { + if peer.Namespaces.Match == crdv1alpha1.NamespaceMatchSelf { + perNSPeers = append(perNSPeers, peer) + } else if len(peer.Namespaces.SameLabels) > 0 { + nsLabelPeers = append(nsLabelPeers, peer) + } } else { clusterPeers = append(clusterPeers, peer) } } - return clusterPeers, perNSPeers + return clusterPeers, perNSPeers, nsLabelPeers } // getAffectedNamespacesForAppliedTo computes the Namespaces currently affected by the appliedTo -// Namespace selectors. -func (n *NetworkPolicyController) getAffectedNamespacesForAppliedTo(appliedTo crdv1alpha1.AppliedTo) []string { - var affectedNS []string +// Namespace selectors, and returns these Namespaces along with their labels. +func (n *NetworkPolicyController) getAffectedNamespacesForAppliedTo(appliedTo crdv1alpha1.AppliedTo) map[string]map[string]string { + affectedNSAndLabels := map[string]map[string]string{} nsLabelSelector := appliedTo.NamespaceSelector if appliedTo.Group != "" { cg, err := n.cgLister.Get(appliedTo.Group) if err != nil { - return affectedNS + return affectedNSAndLabels } if cg.Spec.NamespaceSelector != nil || cg.Spec.PodSelector != nil { nsLabelSelector = cg.Spec.NamespaceSelector @@ -546,9 +663,9 @@ func (n *NetworkPolicyController) getAffectedNamespacesForAppliedTo(appliedTo cr } namespaces, _ := n.namespaceLister.List(nsSel) for _, ns := range namespaces { - affectedNS = append(affectedNS, ns.Name) + affectedNSAndLabels[ns.Name] = ns.Labels } - return affectedNS + return affectedNSAndLabels } // processInternalGroupForRule examines the internal group (and its childGroups if applicable) diff --git a/pkg/controller/networkpolicy/clusternetworkpolicy_test.go b/pkg/controller/networkpolicy/clusternetworkpolicy_test.go index d47e155c9c1..2cbba224964 100644 --- a/pkg/controller/networkpolicy/clusternetworkpolicy_test.go +++ b/pkg/controller/networkpolicy/clusternetworkpolicy_test.go @@ -17,6 +17,8 @@ package networkpolicy import ( "fmt" "net" + "reflect" + "sort" "testing" "github.com/stretchr/testify/assert" @@ -34,6 +36,48 @@ import ( "antrea.io/antrea/pkg/util/k8s" ) +// ruleSemanticallyEqual compares two NetworkPolicyRule objects. It disregards +// the appliedToGroup slice element order as long as two rules' appliedToGroups +// have same elements. +func ruleSemanticallyEqual(a, b controlplane.NetworkPolicyRule) bool { + sort.Strings(a.AppliedToGroups) + sort.Strings(b.AppliedToGroups) + return reflect.DeepEqual(a, b) +} + +// diffNetworkPolicyRuleList checks if elements in two controlplane.NetworkPolicyRule +// slices are equal. If not, it returns the unmatched NetworkPolicyRules. +func diffNetworkPolicyRuleList(a, b []controlplane.NetworkPolicyRule) (extraA, extraB []controlplane.NetworkPolicyRule) { + if len(a) != len(b) { + return nil, nil + } + // Mark indexes in b that has already matched + visited := make([]bool, len(b)) + for i := 0; i < len(a); i++ { + found := false + for j := 0; j < len(b); j++ { + if visited[j] { + continue + } + if ruleSemanticallyEqual(a[i], b[j]) { + visited[j] = true + found = true + break + } + } + if !found { + extraA = append(extraA, a[i]) + } + } + for j := 0; j < len(b); j++ { + if visited[j] { + continue + } + extraB = append(extraB, b[j]) + } + return +} + func TestProcessClusterNetworkPolicy(t *testing.T) { p10 := float64(10) t10 := int32(10) @@ -56,6 +100,12 @@ func TestProcessClusterNetworkPolicy(t *testing.T) { Labels: map[string]string{"foo2": "bar2"}, }, } + nsC := v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "nsC", + Labels: map[string]string{"foo2": "bar2"}, + }, + } svcA := v1.Service{ ObjectMeta: metav1.ObjectMeta{ @@ -797,6 +847,21 @@ func TestProcessClusterNetworkPolicy(t *testing.T) { Priority: 0, Action: &allowAction, }, + { + Direction: controlplane.DirectionIn, + AppliedToGroups: []string{getNormalizedUID(antreatypes.NewGroupSelector("nsC", nil, nil, nil, nil).NormalizedName)}, + From: controlplane.NetworkPolicyPeer{ + AddressGroups: []string{getNormalizedUID(antreatypes.NewGroupSelector("nsC", nil, nil, nil, nil).NormalizedName)}, + }, + Services: []controlplane.Service{ + { + Protocol: &protocolTCP, + Port: &int80, + }, + }, + Priority: 0, + Action: &allowAction, + }, { Direction: controlplane.DirectionIn, AppliedToGroups: []string{getNormalizedUID(antreatypes.NewGroupSelector("", nil, &metav1.LabelSelector{}, nil, nil).NormalizedName)}, @@ -816,12 +881,13 @@ func TestProcessClusterNetworkPolicy(t *testing.T) { AppliedToGroups: []string{ getNormalizedUID(antreatypes.NewGroupSelector("nsA", nil, nil, nil, nil).NormalizedName), getNormalizedUID(antreatypes.NewGroupSelector("nsB", nil, nil, nil, nil).NormalizedName), + getNormalizedUID(antreatypes.NewGroupSelector("nsC", nil, nil, nil, nil).NormalizedName), getNormalizedUID(antreatypes.NewGroupSelector("", nil, &metav1.LabelSelector{}, nil, nil).NormalizedName), }, AppliedToPerRule: true, }, - expectedAppliedToGroups: 3, - expectedAddressGroups: 3, + expectedAppliedToGroups: 4, + expectedAddressGroups: 4, }, { name: "with-per-namespace-rule-applied-to-per-rule", @@ -916,15 +982,103 @@ func TestProcessClusterNetworkPolicy(t *testing.T) { Priority: 1, Action: &dropAction, }, + { + Direction: controlplane.DirectionIn, + AppliedToGroups: []string{getNormalizedUID(antreatypes.NewGroupSelector("nsC", nil, nil, nil, nil).NormalizedName)}, + From: controlplane.NetworkPolicyPeer{ + AddressGroups: []string{getNormalizedUID(antreatypes.NewGroupSelector("nsC", nil, nil, nil, nil).NormalizedName)}, + }, + Services: []controlplane.Service{ + { + Protocol: &protocolTCP, + Port: &int81, + }, + }, + Priority: 1, + Action: &dropAction, + }, }, AppliedToGroups: []string{ getNormalizedUID(antreatypes.NewGroupSelector("nsA", &selectorA, nil, nil, nil).NormalizedName), getNormalizedUID(antreatypes.NewGroupSelector("nsB", nil, nil, nil, nil).NormalizedName), + getNormalizedUID(antreatypes.NewGroupSelector("nsC", nil, nil, nil, nil).NormalizedName), }, AppliedToPerRule: true, }, - expectedAppliedToGroups: 2, - expectedAddressGroups: 2, + expectedAppliedToGroups: 3, + expectedAddressGroups: 3, + }, + { + name: "with-same-labels-namespace-rule", + inputPolicy: &crdv1alpha1.ClusterNetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{Name: "cnpS", UID: "uidS"}, + Spec: crdv1alpha1.ClusterNetworkPolicySpec{ + AppliedTo: []crdv1alpha1.AppliedTo{ + { + NamespaceSelector: &metav1.LabelSelector{}, + }, + }, + Priority: p10, + Ingress: []crdv1alpha1.Rule{ + { + Ports: []crdv1alpha1.NetworkPolicyPort{ + { + Port: &int80, + }, + }, + From: []crdv1alpha1.NetworkPolicyPeer{ + { + Namespaces: &crdv1alpha1.PeerNamespaces{ + SameLabels: []string{"foo2"}, + }, + }, + }, + Action: &allowAction, + }, + }, + }, + }, + expectedPolicy: &antreatypes.NetworkPolicy{ + UID: "uidS", + Name: "uidS", + SourceRef: &controlplane.NetworkPolicyReference{ + Type: controlplane.AntreaClusterNetworkPolicy, + Name: "cnpS", + UID: "uidS", + }, + Priority: &p10, + TierPriority: &DefaultTierPriority, + Rules: []controlplane.NetworkPolicyRule{ + { + Direction: controlplane.DirectionIn, + AppliedToGroups: []string{ + getNormalizedUID(antreatypes.NewGroupSelector("nsC", nil, nil, nil, nil).NormalizedName), + getNormalizedUID(antreatypes.NewGroupSelector("nsB", nil, nil, nil, nil).NormalizedName), + }, + From: controlplane.NetworkPolicyPeer{ + AddressGroups: []string{ + getNormalizedUID(antreatypes.NewGroupSelector("", nil, &selectorB, nil, nil).NormalizedName), + }, + }, + Services: []controlplane.Service{ + { + Protocol: &protocolTCP, + Port: &int80, + }, + }, + Priority: 0, + Action: &allowAction, + }, + }, + AppliedToGroups: []string{ + getNormalizedUID(antreatypes.NewGroupSelector("nsA", nil, nil, nil, nil).NormalizedName), + getNormalizedUID(antreatypes.NewGroupSelector("nsB", nil, nil, nil, nil).NormalizedName), + getNormalizedUID(antreatypes.NewGroupSelector("nsC", nil, nil, nil, nil).NormalizedName), + }, + AppliedToPerRule: true, + }, + expectedAppliedToGroups: 3, + expectedAddressGroups: 1, }, { name: "rule-with-to-service", @@ -1633,6 +1787,7 @@ func TestProcessClusterNetworkPolicy(t *testing.T) { c.cgStore.Add(&cgA) c.namespaceStore.Add(&nsA) c.namespaceStore.Add(&nsB) + c.namespaceStore.Add(&nsC) c.serviceStore.Add(&svcA) c.tierStore.Add(&tierA) actualPolicy, actualAppliedToGroups, actualAddressGroups := c.processClusterNetworkPolicy(tt.inputPolicy) @@ -1642,7 +1797,10 @@ func TestProcessClusterNetworkPolicy(t *testing.T) { assert.Equal(t, tt.expectedPolicy.Priority, actualPolicy.Priority) assert.Equal(t, tt.expectedPolicy.TierPriority, actualPolicy.TierPriority) assert.Equal(t, tt.expectedPolicy.AppliedToPerRule, actualPolicy.AppliedToPerRule) - assert.ElementsMatch(t, tt.expectedPolicy.Rules, actualPolicy.Rules) + missingExpectedRules, extraActualRules := diffNetworkPolicyRuleList(tt.expectedPolicy.Rules, actualPolicy.Rules) + if len(missingExpectedRules) > 0 || len(extraActualRules) > 0 { + t.Errorf("Unexpected rules in processed policy. Missing expected rules: %v. Extra actual rules: %v", missingExpectedRules, extraActualRules) + } assert.ElementsMatch(t, tt.expectedPolicy.AppliedToGroups, actualPolicy.AppliedToGroups) assert.Equal(t, tt.expectedAppliedToGroups, len(actualAppliedToGroups)) assert.Equal(t, tt.expectedAddressGroups, len(actualAddressGroups))