From 576b080a4385677413d80a1c26c4a8fef818bed6 Mon Sep 17 00:00:00 2001 From: Abhishek Raut Date: Wed, 10 Aug 2022 08:15:20 -0700 Subject: [PATCH] Add support for Namespaced Group CRD (#2438) Add Group CRD which is responsible for collecting Pods and Namespaces in the Group's Namespace based on labelselectors defined in the Group definition. It also allows setting an IPBlock and ChildGroups (cannot be set with other Selectors) in the Group. The purpose of a Group is to allow grouping of resources and then be referenced in AntreaNetworkPolicies without having to add the same selectors in every ANP when the group of resources are meant to be shared. This allows for greater sharing and decouples the job of reconciling effective group members from that of enforcing security policies. This PR adds the following: -Group API types -Group CRD YAML -Controller changes to reconcile effective members of a Group -Controller changes to trigger ANP update introduced by a Group -Validation webhook to validate a GroupSpec -NetworkPolicyStatus refactored with Conditions Signed-off-by: Qiyue Yao Co-authored-by: Qiyue Yao Co-authored-by: abhiraut --- .../antrea/crds/clusternetworkpolicy.yaml | 15 + build/charts/antrea/crds/networkpolicy.yaml | 25 + .../templates/controller/clusterrole.yaml | 2 + build/charts/antrea/templates/crds/group.yaml | 128 ++++ .../webhooks/validating/crdvalidator.yaml | 15 + build/yamls/antrea-aks.yml | 187 +++++ build/yamls/antrea-crds.yml | 40 + build/yamls/antrea-eks.yml | 187 +++++ build/yamls/antrea-gke.yml | 187 +++++ build/yamls/antrea-ipsec.yml | 187 +++++ build/yamls/antrea.yml | 187 +++++ cmd/antrea-controller/controller.go | 3 + docs/antrea-network-policy.md | 188 ++++- pkg/apis/controlplane/helper.go | 16 + pkg/apis/controlplane/helper_test.go | 116 +++ pkg/apis/crd/v1alpha1/types.go | 29 + .../crd/v1alpha1/zz_generated.deepcopy.go | 28 +- pkg/apis/crd/v1alpha3/register.go | 2 + pkg/apis/crd/v1alpha3/types.go | 27 + .../crd/v1alpha3/zz_generated.deepcopy.go | 61 ++ pkg/apiserver/apiserver.go | 1 + .../networkpolicy/groupassociation/rest.go | 2 +- .../groupassociation/rest_test.go | 21 +- .../typed/crd/v1alpha3/crd_client.go | 5 + .../crd/v1alpha3/fake/fake_crd_client.go | 4 + .../typed/crd/v1alpha3/fake/fake_group.go | 140 ++++ .../typed/crd/v1alpha3/generated_expansion.go | 2 + .../versioned/typed/crd/v1alpha3/group.go | 193 +++++ .../externalversions/crd/v1alpha3/group.go | 88 +++ .../crd/v1alpha3/interface.go | 7 + .../informers/externalversions/generic.go | 2 + .../crd/v1alpha3/expansion_generated.go | 8 + pkg/client/listers/crd/v1alpha3/group.go | 97 +++ pkg/controller/grouping/group_entity_index.go | 2 +- .../networkpolicy/antreanetworkpolicy.go | 102 ++- .../networkpolicy/antreanetworkpolicy_test.go | 74 ++ pkg/controller/networkpolicy/clustergroup.go | 73 +- .../networkpolicy/clustergroup_test.go | 244 +++++-- .../networkpolicy/clusternetworkpolicy.go | 37 +- .../clusternetworkpolicy_test.go | 2 +- pkg/controller/networkpolicy/crd_utils.go | 65 +- .../networkpolicy/crd_utils_test.go | 147 ++++ pkg/controller/networkpolicy/group.go | 282 +++++++ pkg/controller/networkpolicy/group_test.go | 532 ++++++++++++++ .../networkpolicy/networkpolicy_controller.go | 100 ++- .../networkpolicy_controller_test.go | 16 + .../networkpolicy/status_controller.go | 49 +- .../networkpolicy/status_controller_test.go | 30 +- pkg/controller/networkpolicy/store/group.go | 12 +- pkg/controller/networkpolicy/validate.go | 142 +++- pkg/controller/networkpolicy/validate_test.go | 43 +- pkg/controller/types/group.go | 6 +- pkg/controller/types/networkpolicy.go | 3 + test/e2e/antreapolicy_test.go | 686 +++++++++++++++++- test/e2e/flowaggregator_test.go | 12 +- test/e2e/group_test.go | 284 ++++++++ test/e2e/k8s_util.go | 91 +++ test/e2e/utils/anp_spec_builder.go | 24 +- test/e2e/utils/grpspecbuilder.go | 100 +++ 59 files changed, 5086 insertions(+), 272 deletions(-) create mode 100644 build/charts/antrea/templates/crds/group.yaml create mode 100644 pkg/apis/controlplane/helper_test.go create mode 100644 pkg/client/clientset/versioned/typed/crd/v1alpha3/fake/fake_group.go create mode 100644 pkg/client/clientset/versioned/typed/crd/v1alpha3/group.go create mode 100644 pkg/client/informers/externalversions/crd/v1alpha3/group.go create mode 100644 pkg/client/listers/crd/v1alpha3/group.go create mode 100644 pkg/controller/networkpolicy/group.go create mode 100644 pkg/controller/networkpolicy/group_test.go create mode 100644 test/e2e/group_test.go create mode 100644 test/e2e/utils/grpspecbuilder.go diff --git a/build/charts/antrea/crds/clusternetworkpolicy.yaml b/build/charts/antrea/crds/clusternetworkpolicy.yaml index f49409092eb..417e827cbf3 100644 --- a/build/charts/antrea/crds/clusternetworkpolicy.yaml +++ b/build/charts/antrea/crds/clusternetworkpolicy.yaml @@ -616,6 +616,21 @@ spec: type: integer desiredNodesRealized: type: integer + conditions: + type: array + items: + type: object + properties: + type: + type: string + status: + type: string + lastTransitionTime: + type: string + reason: + type: string + message: + type: string subresources: status: {} scope: Cluster diff --git a/build/charts/antrea/crds/networkpolicy.yaml b/build/charts/antrea/crds/networkpolicy.yaml index e087638e3e5..bbf144f9f1c 100644 --- a/build/charts/antrea/crds/networkpolicy.yaml +++ b/build/charts/antrea/crds/networkpolicy.yaml @@ -81,6 +81,8 @@ spec: pattern: "^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$" matchLabels: x-kubernetes-preserve-unknown-fields: true + group: + type: string ingress: type: array items: @@ -118,6 +120,8 @@ spec: pattern: "^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$" matchLabels: x-kubernetes-preserve-unknown-fields: true + group: + type: string # Ensure that Action field allows only ALLOW, DROP, REJECT and PASS values action: type: string @@ -272,6 +276,8 @@ spec: type: array matchLabels: x-kubernetes-preserve-unknown-fields: true + group: + type: string name: type: string enableLogging: @@ -313,6 +319,8 @@ spec: pattern: "^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$" matchLabels: x-kubernetes-preserve-unknown-fields: true + group: + type: string # Ensure that Action field allows only ALLOW, DROP, REJECT and PASS values action: type: string @@ -471,6 +479,8 @@ spec: type: array matchLabels: x-kubernetes-preserve-unknown-fields: true + group: + type: string toServices: type: array items: @@ -497,6 +507,21 @@ spec: type: integer desiredNodesRealized: type: integer + conditions: + type: array + items: + type: object + properties: + type: + type: string + status: + type: string + lastTransitionTime: + type: string + reason: + type: string + message: + type: string subresources: status: {} scope: Namespaced diff --git a/build/charts/antrea/templates/controller/clusterrole.yaml b/build/charts/antrea/templates/controller/clusterrole.yaml index b5d13088ae0..e41fc0c84df 100644 --- a/build/charts/antrea/templates/controller/clusterrole.yaml +++ b/build/charts/antrea/templates/controller/clusterrole.yaml @@ -230,6 +230,7 @@ rules: resources: - externalentities - clustergroups + - groups verbs: - get - watch @@ -242,6 +243,7 @@ rules: - crd.antrea.io resources: - clustergroups/status + - groups/status verbs: - update - apiGroups: diff --git a/build/charts/antrea/templates/crds/group.yaml b/build/charts/antrea/templates/crds/group.yaml new file mode 100644 index 00000000000..043446ea43f --- /dev/null +++ b/build/charts/antrea/templates/crds/group.yaml @@ -0,0 +1,128 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: groups.crd.antrea.io +spec: + group: crd.antrea.io + versions: + - name: v1alpha3 + served: true + storage: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + childGroups: + type: array + items: + type: string + podSelector: + type: object + properties: + matchExpressions: + type: array + items: + type: object + properties: + key: + type: string + operator: + enum: + - In + - NotIn + - Exists + - DoesNotExist + type: string + values: + type: array + items: + type: string + matchLabels: + x-kubernetes-preserve-unknown-fields: true + namespaceSelector: + type: object + properties: + matchExpressions: + type: array + items: + type: object + properties: + key: + type: string + operator: + enum: + - In + - NotIn + - Exists + - DoesNotExist + type: string + values: + type: array + items: + type: string + matchLabels: + x-kubernetes-preserve-unknown-fields: true + externalEntitySelector: + type: object + properties: + matchExpressions: + type: array + items: + type: object + properties: + key: + type: string + operator: + enum: + - In + - NotIn + - Exists + - DoesNotExist + type: string + values: + type: array + items: + type: string + matchLabels: + x-kubernetes-preserve-unknown-fields: true + ipBlocks: + type: array + items: + type: object + properties: + cidr: + type: string + format: cidr + serviceReference: + type: object + properties: + name: + type: string + namespace: + type: string + status: + type: object + properties: + conditions: + type: array + items: + type: object + properties: + type: + type: string + status: + type: string + lastTransitionTime: + type: string + subresources: + status: {} + scope: Namespaced + names: + plural: groups + singular: group + kind: Group + shortNames: + - grp diff --git a/build/charts/antrea/templates/webhooks/validating/crdvalidator.yaml b/build/charts/antrea/templates/webhooks/validating/crdvalidator.yaml index 6cd661c3eeb..06460f1bc74 100644 --- a/build/charts/antrea/templates/webhooks/validating/crdvalidator.yaml +++ b/build/charts/antrea/templates/webhooks/validating/crdvalidator.yaml @@ -65,6 +65,21 @@ webhooks: admissionReviewVersions: ["v1", "v1beta1"] sideEffects: None timeoutSeconds: 5 + - name: "groupvalidator.antrea.io" + clientConfig: + service: + name: "antrea" + namespace: "kube-system" + path: "/validate/group" + rules: + - operations: [ "CREATE", "UPDATE" ] + apiGroups: [ "crd.antrea.io" ] + apiVersions: [ "v1alpha3" ] + resources: [ "groups" ] + scope: "Namespaced" + admissionReviewVersions: [ "v1", "v1beta1" ] + sideEffects: None + timeoutSeconds: 5 - name: "externalippoolvalidator.antrea.io" clientConfig: service: diff --git a/build/yamls/antrea-aks.yml b/build/yamls/antrea-aks.yml index 4311a1a48c2..22368303135 100644 --- a/build/yamls/antrea-aks.yml +++ b/build/yamls/antrea-aks.yml @@ -994,6 +994,21 @@ spec: type: integer desiredNodesRealized: type: integer + conditions: + type: array + items: + type: object + properties: + type: + type: string + status: + type: string + lastTransitionTime: + type: string + reason: + type: string + message: + type: string subresources: status: {} scope: Cluster @@ -1505,6 +1520,8 @@ spec: pattern: "^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$" matchLabels: x-kubernetes-preserve-unknown-fields: true + group: + type: string ingress: type: array items: @@ -1542,6 +1559,8 @@ spec: pattern: "^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$" matchLabels: x-kubernetes-preserve-unknown-fields: true + group: + type: string # Ensure that Action field allows only ALLOW, DROP, REJECT and PASS values action: type: string @@ -1696,6 +1715,8 @@ spec: type: array matchLabels: x-kubernetes-preserve-unknown-fields: true + group: + type: string name: type: string enableLogging: @@ -1737,6 +1758,8 @@ spec: pattern: "^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$" matchLabels: x-kubernetes-preserve-unknown-fields: true + group: + type: string # Ensure that Action field allows only ALLOW, DROP, REJECT and PASS values action: type: string @@ -1895,6 +1918,8 @@ spec: type: array matchLabels: x-kubernetes-preserve-unknown-fields: true + group: + type: string toServices: type: array items: @@ -1921,6 +1946,21 @@ spec: type: integer desiredNodesRealized: type: integer + conditions: + type: array + items: + type: object + properties: + type: + type: string + status: + type: string + lastTransitionTime: + type: string + reason: + type: string + message: + type: string subresources: status: {} scope: Namespaced @@ -2992,6 +3032,136 @@ data: # tls.key: selfSignedCA: true --- +# Source: antrea/templates/crds/group.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: groups.crd.antrea.io +spec: + group: crd.antrea.io + versions: + - name: v1alpha3 + served: true + storage: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + childGroups: + type: array + items: + type: string + podSelector: + type: object + properties: + matchExpressions: + type: array + items: + type: object + properties: + key: + type: string + operator: + enum: + - In + - NotIn + - Exists + - DoesNotExist + type: string + values: + type: array + items: + type: string + matchLabels: + x-kubernetes-preserve-unknown-fields: true + namespaceSelector: + type: object + properties: + matchExpressions: + type: array + items: + type: object + properties: + key: + type: string + operator: + enum: + - In + - NotIn + - Exists + - DoesNotExist + type: string + values: + type: array + items: + type: string + matchLabels: + x-kubernetes-preserve-unknown-fields: true + externalEntitySelector: + type: object + properties: + matchExpressions: + type: array + items: + type: object + properties: + key: + type: string + operator: + enum: + - In + - NotIn + - Exists + - DoesNotExist + type: string + values: + type: array + items: + type: string + matchLabels: + x-kubernetes-preserve-unknown-fields: true + ipBlocks: + type: array + items: + type: object + properties: + cidr: + type: string + format: cidr + serviceReference: + type: object + properties: + name: + type: string + namespace: + type: string + status: + type: object + properties: + conditions: + type: array + items: + type: object + properties: + type: + type: string + status: + type: string + lastTransitionTime: + type: string + subresources: + status: {} + scope: Namespaced + names: + plural: groups + singular: group + kind: Group + shortNames: + - grp +--- # Source: antrea/templates/agent/clusterrole.yaml kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 @@ -3511,6 +3681,7 @@ rules: resources: - externalentities - clustergroups + - groups verbs: - get - watch @@ -3523,6 +3694,7 @@ rules: - crd.antrea.io resources: - clustergroups/status + - groups/status verbs: - update - apiGroups: @@ -4234,6 +4406,21 @@ webhooks: admissionReviewVersions: ["v1", "v1beta1"] sideEffects: None timeoutSeconds: 5 + - name: "groupvalidator.antrea.io" + clientConfig: + service: + name: "antrea" + namespace: "kube-system" + path: "/validate/group" + rules: + - operations: [ "CREATE", "UPDATE" ] + apiGroups: [ "crd.antrea.io" ] + apiVersions: [ "v1alpha3" ] + resources: [ "groups" ] + scope: "Namespaced" + admissionReviewVersions: [ "v1", "v1beta1" ] + sideEffects: None + timeoutSeconds: 5 - name: "externalippoolvalidator.antrea.io" clientConfig: service: diff --git a/build/yamls/antrea-crds.yml b/build/yamls/antrea-crds.yml index f90cdd94625..babeaa77fe6 100644 --- a/build/yamls/antrea-crds.yml +++ b/build/yamls/antrea-crds.yml @@ -987,6 +987,21 @@ spec: type: integer desiredNodesRealized: type: integer + conditions: + type: array + items: + type: object + properties: + type: + type: string + status: + type: string + lastTransitionTime: + type: string + reason: + type: string + message: + type: string subresources: status: {} scope: Cluster @@ -1488,6 +1503,8 @@ spec: pattern: "^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$" matchLabels: x-kubernetes-preserve-unknown-fields: true + group: + type: string ingress: type: array items: @@ -1525,6 +1542,8 @@ spec: pattern: "^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$" matchLabels: x-kubernetes-preserve-unknown-fields: true + group: + type: string # Ensure that Action field allows only ALLOW, DROP, REJECT and PASS values action: type: string @@ -1679,6 +1698,8 @@ spec: type: array matchLabels: x-kubernetes-preserve-unknown-fields: true + group: + type: string name: type: string enableLogging: @@ -1720,6 +1741,8 @@ spec: pattern: "^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$" matchLabels: x-kubernetes-preserve-unknown-fields: true + group: + type: string # Ensure that Action field allows only ALLOW, DROP, REJECT and PASS values action: type: string @@ -1878,6 +1901,8 @@ spec: type: array matchLabels: x-kubernetes-preserve-unknown-fields: true + group: + type: string toServices: type: array items: @@ -1904,6 +1929,21 @@ spec: type: integer desiredNodesRealized: type: integer + conditions: + type: array + items: + type: object + properties: + type: + type: string + status: + type: string + lastTransitionTime: + type: string + reason: + type: string + message: + type: string subresources: status: {} scope: Namespaced diff --git a/build/yamls/antrea-eks.yml b/build/yamls/antrea-eks.yml index b06179ad8c6..35e24170174 100644 --- a/build/yamls/antrea-eks.yml +++ b/build/yamls/antrea-eks.yml @@ -994,6 +994,21 @@ spec: type: integer desiredNodesRealized: type: integer + conditions: + type: array + items: + type: object + properties: + type: + type: string + status: + type: string + lastTransitionTime: + type: string + reason: + type: string + message: + type: string subresources: status: {} scope: Cluster @@ -1505,6 +1520,8 @@ spec: pattern: "^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$" matchLabels: x-kubernetes-preserve-unknown-fields: true + group: + type: string ingress: type: array items: @@ -1542,6 +1559,8 @@ spec: pattern: "^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$" matchLabels: x-kubernetes-preserve-unknown-fields: true + group: + type: string # Ensure that Action field allows only ALLOW, DROP, REJECT and PASS values action: type: string @@ -1696,6 +1715,8 @@ spec: type: array matchLabels: x-kubernetes-preserve-unknown-fields: true + group: + type: string name: type: string enableLogging: @@ -1737,6 +1758,8 @@ spec: pattern: "^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$" matchLabels: x-kubernetes-preserve-unknown-fields: true + group: + type: string # Ensure that Action field allows only ALLOW, DROP, REJECT and PASS values action: type: string @@ -1895,6 +1918,8 @@ spec: type: array matchLabels: x-kubernetes-preserve-unknown-fields: true + group: + type: string toServices: type: array items: @@ -1921,6 +1946,21 @@ spec: type: integer desiredNodesRealized: type: integer + conditions: + type: array + items: + type: object + properties: + type: + type: string + status: + type: string + lastTransitionTime: + type: string + reason: + type: string + message: + type: string subresources: status: {} scope: Namespaced @@ -2992,6 +3032,136 @@ data: # tls.key: selfSignedCA: true --- +# Source: antrea/templates/crds/group.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: groups.crd.antrea.io +spec: + group: crd.antrea.io + versions: + - name: v1alpha3 + served: true + storage: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + childGroups: + type: array + items: + type: string + podSelector: + type: object + properties: + matchExpressions: + type: array + items: + type: object + properties: + key: + type: string + operator: + enum: + - In + - NotIn + - Exists + - DoesNotExist + type: string + values: + type: array + items: + type: string + matchLabels: + x-kubernetes-preserve-unknown-fields: true + namespaceSelector: + type: object + properties: + matchExpressions: + type: array + items: + type: object + properties: + key: + type: string + operator: + enum: + - In + - NotIn + - Exists + - DoesNotExist + type: string + values: + type: array + items: + type: string + matchLabels: + x-kubernetes-preserve-unknown-fields: true + externalEntitySelector: + type: object + properties: + matchExpressions: + type: array + items: + type: object + properties: + key: + type: string + operator: + enum: + - In + - NotIn + - Exists + - DoesNotExist + type: string + values: + type: array + items: + type: string + matchLabels: + x-kubernetes-preserve-unknown-fields: true + ipBlocks: + type: array + items: + type: object + properties: + cidr: + type: string + format: cidr + serviceReference: + type: object + properties: + name: + type: string + namespace: + type: string + status: + type: object + properties: + conditions: + type: array + items: + type: object + properties: + type: + type: string + status: + type: string + lastTransitionTime: + type: string + subresources: + status: {} + scope: Namespaced + names: + plural: groups + singular: group + kind: Group + shortNames: + - grp +--- # Source: antrea/templates/agent/clusterrole.yaml kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 @@ -3511,6 +3681,7 @@ rules: resources: - externalentities - clustergroups + - groups verbs: - get - watch @@ -3523,6 +3694,7 @@ rules: - crd.antrea.io resources: - clustergroups/status + - groups/status verbs: - update - apiGroups: @@ -4236,6 +4408,21 @@ webhooks: admissionReviewVersions: ["v1", "v1beta1"] sideEffects: None timeoutSeconds: 5 + - name: "groupvalidator.antrea.io" + clientConfig: + service: + name: "antrea" + namespace: "kube-system" + path: "/validate/group" + rules: + - operations: [ "CREATE", "UPDATE" ] + apiGroups: [ "crd.antrea.io" ] + apiVersions: [ "v1alpha3" ] + resources: [ "groups" ] + scope: "Namespaced" + admissionReviewVersions: [ "v1", "v1beta1" ] + sideEffects: None + timeoutSeconds: 5 - name: "externalippoolvalidator.antrea.io" clientConfig: service: diff --git a/build/yamls/antrea-gke.yml b/build/yamls/antrea-gke.yml index be9b7d2b255..b7c5d043eeb 100644 --- a/build/yamls/antrea-gke.yml +++ b/build/yamls/antrea-gke.yml @@ -994,6 +994,21 @@ spec: type: integer desiredNodesRealized: type: integer + conditions: + type: array + items: + type: object + properties: + type: + type: string + status: + type: string + lastTransitionTime: + type: string + reason: + type: string + message: + type: string subresources: status: {} scope: Cluster @@ -1505,6 +1520,8 @@ spec: pattern: "^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$" matchLabels: x-kubernetes-preserve-unknown-fields: true + group: + type: string ingress: type: array items: @@ -1542,6 +1559,8 @@ spec: pattern: "^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$" matchLabels: x-kubernetes-preserve-unknown-fields: true + group: + type: string # Ensure that Action field allows only ALLOW, DROP, REJECT and PASS values action: type: string @@ -1696,6 +1715,8 @@ spec: type: array matchLabels: x-kubernetes-preserve-unknown-fields: true + group: + type: string name: type: string enableLogging: @@ -1737,6 +1758,8 @@ spec: pattern: "^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$" matchLabels: x-kubernetes-preserve-unknown-fields: true + group: + type: string # Ensure that Action field allows only ALLOW, DROP, REJECT and PASS values action: type: string @@ -1895,6 +1918,8 @@ spec: type: array matchLabels: x-kubernetes-preserve-unknown-fields: true + group: + type: string toServices: type: array items: @@ -1921,6 +1946,21 @@ spec: type: integer desiredNodesRealized: type: integer + conditions: + type: array + items: + type: object + properties: + type: + type: string + status: + type: string + lastTransitionTime: + type: string + reason: + type: string + message: + type: string subresources: status: {} scope: Namespaced @@ -2992,6 +3032,136 @@ data: # tls.key: selfSignedCA: true --- +# Source: antrea/templates/crds/group.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: groups.crd.antrea.io +spec: + group: crd.antrea.io + versions: + - name: v1alpha3 + served: true + storage: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + childGroups: + type: array + items: + type: string + podSelector: + type: object + properties: + matchExpressions: + type: array + items: + type: object + properties: + key: + type: string + operator: + enum: + - In + - NotIn + - Exists + - DoesNotExist + type: string + values: + type: array + items: + type: string + matchLabels: + x-kubernetes-preserve-unknown-fields: true + namespaceSelector: + type: object + properties: + matchExpressions: + type: array + items: + type: object + properties: + key: + type: string + operator: + enum: + - In + - NotIn + - Exists + - DoesNotExist + type: string + values: + type: array + items: + type: string + matchLabels: + x-kubernetes-preserve-unknown-fields: true + externalEntitySelector: + type: object + properties: + matchExpressions: + type: array + items: + type: object + properties: + key: + type: string + operator: + enum: + - In + - NotIn + - Exists + - DoesNotExist + type: string + values: + type: array + items: + type: string + matchLabels: + x-kubernetes-preserve-unknown-fields: true + ipBlocks: + type: array + items: + type: object + properties: + cidr: + type: string + format: cidr + serviceReference: + type: object + properties: + name: + type: string + namespace: + type: string + status: + type: object + properties: + conditions: + type: array + items: + type: object + properties: + type: + type: string + status: + type: string + lastTransitionTime: + type: string + subresources: + status: {} + scope: Namespaced + names: + plural: groups + singular: group + kind: Group + shortNames: + - grp +--- # Source: antrea/templates/agent/clusterrole.yaml kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 @@ -3511,6 +3681,7 @@ rules: resources: - externalentities - clustergroups + - groups verbs: - get - watch @@ -3523,6 +3694,7 @@ rules: - crd.antrea.io resources: - clustergroups/status + - groups/status verbs: - update - apiGroups: @@ -4233,6 +4405,21 @@ webhooks: admissionReviewVersions: ["v1", "v1beta1"] sideEffects: None timeoutSeconds: 5 + - name: "groupvalidator.antrea.io" + clientConfig: + service: + name: "antrea" + namespace: "kube-system" + path: "/validate/group" + rules: + - operations: [ "CREATE", "UPDATE" ] + apiGroups: [ "crd.antrea.io" ] + apiVersions: [ "v1alpha3" ] + resources: [ "groups" ] + scope: "Namespaced" + admissionReviewVersions: [ "v1", "v1beta1" ] + sideEffects: None + timeoutSeconds: 5 - name: "externalippoolvalidator.antrea.io" clientConfig: service: diff --git a/build/yamls/antrea-ipsec.yml b/build/yamls/antrea-ipsec.yml index ac3a844b649..4a3173160e0 100644 --- a/build/yamls/antrea-ipsec.yml +++ b/build/yamls/antrea-ipsec.yml @@ -994,6 +994,21 @@ spec: type: integer desiredNodesRealized: type: integer + conditions: + type: array + items: + type: object + properties: + type: + type: string + status: + type: string + lastTransitionTime: + type: string + reason: + type: string + message: + type: string subresources: status: {} scope: Cluster @@ -1505,6 +1520,8 @@ spec: pattern: "^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$" matchLabels: x-kubernetes-preserve-unknown-fields: true + group: + type: string ingress: type: array items: @@ -1542,6 +1559,8 @@ spec: pattern: "^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$" matchLabels: x-kubernetes-preserve-unknown-fields: true + group: + type: string # Ensure that Action field allows only ALLOW, DROP, REJECT and PASS values action: type: string @@ -1696,6 +1715,8 @@ spec: type: array matchLabels: x-kubernetes-preserve-unknown-fields: true + group: + type: string name: type: string enableLogging: @@ -1737,6 +1758,8 @@ spec: pattern: "^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$" matchLabels: x-kubernetes-preserve-unknown-fields: true + group: + type: string # Ensure that Action field allows only ALLOW, DROP, REJECT and PASS values action: type: string @@ -1895,6 +1918,8 @@ spec: type: array matchLabels: x-kubernetes-preserve-unknown-fields: true + group: + type: string toServices: type: array items: @@ -1921,6 +1946,21 @@ spec: type: integer desiredNodesRealized: type: integer + conditions: + type: array + items: + type: object + properties: + type: + type: string + status: + type: string + lastTransitionTime: + type: string + reason: + type: string + message: + type: string subresources: status: {} scope: Namespaced @@ -3005,6 +3045,136 @@ data: # tls.key: selfSignedCA: true --- +# Source: antrea/templates/crds/group.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: groups.crd.antrea.io +spec: + group: crd.antrea.io + versions: + - name: v1alpha3 + served: true + storage: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + childGroups: + type: array + items: + type: string + podSelector: + type: object + properties: + matchExpressions: + type: array + items: + type: object + properties: + key: + type: string + operator: + enum: + - In + - NotIn + - Exists + - DoesNotExist + type: string + values: + type: array + items: + type: string + matchLabels: + x-kubernetes-preserve-unknown-fields: true + namespaceSelector: + type: object + properties: + matchExpressions: + type: array + items: + type: object + properties: + key: + type: string + operator: + enum: + - In + - NotIn + - Exists + - DoesNotExist + type: string + values: + type: array + items: + type: string + matchLabels: + x-kubernetes-preserve-unknown-fields: true + externalEntitySelector: + type: object + properties: + matchExpressions: + type: array + items: + type: object + properties: + key: + type: string + operator: + enum: + - In + - NotIn + - Exists + - DoesNotExist + type: string + values: + type: array + items: + type: string + matchLabels: + x-kubernetes-preserve-unknown-fields: true + ipBlocks: + type: array + items: + type: object + properties: + cidr: + type: string + format: cidr + serviceReference: + type: object + properties: + name: + type: string + namespace: + type: string + status: + type: object + properties: + conditions: + type: array + items: + type: object + properties: + type: + type: string + status: + type: string + lastTransitionTime: + type: string + subresources: + status: {} + scope: Namespaced + names: + plural: groups + singular: group + kind: Group + shortNames: + - grp +--- # Source: antrea/templates/agent/clusterrole.yaml kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 @@ -3524,6 +3694,7 @@ rules: resources: - externalentities - clustergroups + - groups verbs: - get - watch @@ -3536,6 +3707,7 @@ rules: - crd.antrea.io resources: - clustergroups/status + - groups/status verbs: - update - apiGroups: @@ -4292,6 +4464,21 @@ webhooks: admissionReviewVersions: ["v1", "v1beta1"] sideEffects: None timeoutSeconds: 5 + - name: "groupvalidator.antrea.io" + clientConfig: + service: + name: "antrea" + namespace: "kube-system" + path: "/validate/group" + rules: + - operations: [ "CREATE", "UPDATE" ] + apiGroups: [ "crd.antrea.io" ] + apiVersions: [ "v1alpha3" ] + resources: [ "groups" ] + scope: "Namespaced" + admissionReviewVersions: [ "v1", "v1beta1" ] + sideEffects: None + timeoutSeconds: 5 - name: "externalippoolvalidator.antrea.io" clientConfig: service: diff --git a/build/yamls/antrea.yml b/build/yamls/antrea.yml index 356dc629089..6695e0722b2 100644 --- a/build/yamls/antrea.yml +++ b/build/yamls/antrea.yml @@ -994,6 +994,21 @@ spec: type: integer desiredNodesRealized: type: integer + conditions: + type: array + items: + type: object + properties: + type: + type: string + status: + type: string + lastTransitionTime: + type: string + reason: + type: string + message: + type: string subresources: status: {} scope: Cluster @@ -1505,6 +1520,8 @@ spec: pattern: "^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$" matchLabels: x-kubernetes-preserve-unknown-fields: true + group: + type: string ingress: type: array items: @@ -1542,6 +1559,8 @@ spec: pattern: "^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$" matchLabels: x-kubernetes-preserve-unknown-fields: true + group: + type: string # Ensure that Action field allows only ALLOW, DROP, REJECT and PASS values action: type: string @@ -1696,6 +1715,8 @@ spec: type: array matchLabels: x-kubernetes-preserve-unknown-fields: true + group: + type: string name: type: string enableLogging: @@ -1737,6 +1758,8 @@ spec: pattern: "^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$" matchLabels: x-kubernetes-preserve-unknown-fields: true + group: + type: string # Ensure that Action field allows only ALLOW, DROP, REJECT and PASS values action: type: string @@ -1895,6 +1918,8 @@ spec: type: array matchLabels: x-kubernetes-preserve-unknown-fields: true + group: + type: string toServices: type: array items: @@ -1921,6 +1946,21 @@ spec: type: integer desiredNodesRealized: type: integer + conditions: + type: array + items: + type: object + properties: + type: + type: string + status: + type: string + lastTransitionTime: + type: string + reason: + type: string + message: + type: string subresources: status: {} scope: Namespaced @@ -2992,6 +3032,136 @@ data: # tls.key: selfSignedCA: true --- +# Source: antrea/templates/crds/group.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: groups.crd.antrea.io +spec: + group: crd.antrea.io + versions: + - name: v1alpha3 + served: true + storage: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + childGroups: + type: array + items: + type: string + podSelector: + type: object + properties: + matchExpressions: + type: array + items: + type: object + properties: + key: + type: string + operator: + enum: + - In + - NotIn + - Exists + - DoesNotExist + type: string + values: + type: array + items: + type: string + matchLabels: + x-kubernetes-preserve-unknown-fields: true + namespaceSelector: + type: object + properties: + matchExpressions: + type: array + items: + type: object + properties: + key: + type: string + operator: + enum: + - In + - NotIn + - Exists + - DoesNotExist + type: string + values: + type: array + items: + type: string + matchLabels: + x-kubernetes-preserve-unknown-fields: true + externalEntitySelector: + type: object + properties: + matchExpressions: + type: array + items: + type: object + properties: + key: + type: string + operator: + enum: + - In + - NotIn + - Exists + - DoesNotExist + type: string + values: + type: array + items: + type: string + matchLabels: + x-kubernetes-preserve-unknown-fields: true + ipBlocks: + type: array + items: + type: object + properties: + cidr: + type: string + format: cidr + serviceReference: + type: object + properties: + name: + type: string + namespace: + type: string + status: + type: object + properties: + conditions: + type: array + items: + type: object + properties: + type: + type: string + status: + type: string + lastTransitionTime: + type: string + subresources: + status: {} + scope: Namespaced + names: + plural: groups + singular: group + kind: Group + shortNames: + - grp +--- # Source: antrea/templates/agent/clusterrole.yaml kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 @@ -3511,6 +3681,7 @@ rules: resources: - externalentities - clustergroups + - groups verbs: - get - watch @@ -3523,6 +3694,7 @@ rules: - crd.antrea.io resources: - clustergroups/status + - groups/status verbs: - update - apiGroups: @@ -4233,6 +4405,21 @@ webhooks: admissionReviewVersions: ["v1", "v1beta1"] sideEffects: None timeoutSeconds: 5 + - name: "groupvalidator.antrea.io" + clientConfig: + service: + name: "antrea" + namespace: "kube-system" + path: "/validate/group" + rules: + - operations: [ "CREATE", "UPDATE" ] + apiGroups: [ "crd.antrea.io" ] + apiVersions: [ "v1alpha3" ] + resources: [ "groups" ] + scope: "Namespaced" + admissionReviewVersions: [ "v1", "v1beta1" ] + sideEffects: None + timeoutSeconds: 5 - name: "externalippoolvalidator.antrea.io" clientConfig: service: diff --git a/cmd/antrea-controller/controller.go b/cmd/antrea-controller/controller.go index c1087c124ff..ef662da5574 100644 --- a/cmd/antrea-controller/controller.go +++ b/cmd/antrea-controller/controller.go @@ -102,6 +102,7 @@ var allowedPaths = []string{ "/validate/clustergroup", "/validate/externalippool", "/validate/egress", + "/validate/group", "/validate/ippool", "/convert/clustergroup", } @@ -129,6 +130,7 @@ func run(o *Options) error { tierInformer := crdInformerFactory.Crd().V1alpha1().Tiers() tfInformer := crdInformerFactory.Crd().V1alpha1().Traceflows() cgInformer := crdInformerFactory.Crd().V1alpha3().ClusterGroups() + grpInformer := crdInformerFactory.Crd().V1alpha3().Groups() egressInformer := crdInformerFactory.Crd().V1alpha2().Egresses() externalIPPoolInformer := crdInformerFactory.Crd().V1alpha2().ExternalIPPools() @@ -158,6 +160,7 @@ func run(o *Options) error { anpInformer, tierInformer, cgInformer, + grpInformer, addressGroupStore, appliedToGroupStore, networkPolicyStore, diff --git a/docs/antrea-network-policy.md b/docs/antrea-network-policy.md index fcc18d6eac8..8751363ab48 100644 --- a/docs/antrea-network-policy.md +++ b/docs/antrea-network-policy.md @@ -7,7 +7,7 @@ - [Tier](#tier) - [Tier CRDs](#tier-crds) - [Static tiers](#static-tiers) - - [kubectl commands for Tier](#kubectl-commands-for-tier) + - [kubectl commands for Tier](#kubectl-commands-for-tier) - [Antrea ClusterNetworkPolicy](#antrea-clusternetworkpolicy) - [The Antrea ClusterNetworkPolicy resource](#the-antrea-clusternetworkpolicy-resource) - [ACNP with stand-alone selectors](#acnp-with-stand-alone-selectors) @@ -21,11 +21,12 @@ - [ACNP for multicast egress traffic](#acnp-for-multicast-egress-traffic) - [Behavior of to and from selectors](#behavior-of-to-and-from-selectors) - [Key differences from K8s NetworkPolicy](#key-differences-from-k8s-networkpolicy) - - [kubectl commands for Antrea ClusterNetworkPolicy](#kubectl-commands-for-antrea-clusternetworkpolicy) + - [kubectl commands for Antrea ClusterNetworkPolicy](#kubectl-commands-for-antrea-clusternetworkpolicy) - [Antrea NetworkPolicy](#antrea-networkpolicy) - [The Antrea NetworkPolicy resource](#the-antrea-networkpolicy-resource) - [Key differences from Antrea ClusterNetworkPolicy](#key-differences-from-antrea-clusternetworkpolicy) - - [kubectl commands for Antrea NetworkPolicy](#kubectl-commands-for-antrea-networkpolicy) + - [Antrea NetworkPolicy with Group reference](#antrea-networkpolicy-with-group-reference) + - [kubectl commands for Antrea NetworkPolicy](#kubectl-commands-for-antrea-networkpolicy) - [Antrea-native Policy ordering based on priorities](#antrea-native-policy-ordering-based-on-priorities) - [Ordering based on Tier priority](#ordering-based-on-tier-priority) - [Ordering based on policy priority](#ordering-based-on-policy-priority) @@ -42,7 +43,11 @@ - [Apply to NodePort Service](#apply-to-nodeport-service) - [ClusterGroup](#clustergroup) - [ClusterGroup CRD](#clustergroup-crd) - - [kubectl commands for ClusterGroup](#kubectl-commands-for-clustergroup) + - [kubectl commands for ClusterGroup](#kubectl-commands-for-clustergroup) +- [Group](#group) + - [Group CRD](#group-crd) + - [Restrictions and Key differences from ClusterGroup](#restrictions-and-key-differences-from-clustergroup) + - [kubectl commands for Group](#kubectl-commands-for-group) - [RBAC](#rbac) - [Notes and constraints](#notes-and-constraints) @@ -154,9 +159,9 @@ by creating an Antrea-native policy with an "allow" action in the "baseline" Tie For this reason, it generally does not make sense to create policies in the "baseline" Tier with the "allow" action. -### kubectl commands for Tier +### *kubectl* commands for Tier -The following kubectl commands can be used to retrieve Tier resources: +The following `kubectl` commands can be used to retrieve Tier resources: ```bash # Use long name @@ -774,9 +779,9 @@ expressions, when defining `egress` rules. For more information on its usage, re - Rules assume the priority in which they are written. i.e. rule set at top takes precedence over a rule set below it. -### kubectl commands for Antrea ClusterNetworkPolicy +### *kubectl* commands for Antrea ClusterNetworkPolicy -The following kubectl commands can be used to retrieve ACNP resources: +The following `kubectl` commands can be used to retrieve ACNP resources: ```bash # Use long name @@ -868,14 +873,57 @@ policy CRDs. - `podSelector` without a `namespaceSelector`, set within a NetworkPolicy Peer of any rule, selects Pods from the Namespace in which the Antrea NetworkPolicy is created. This behavior is similar to the K8s NetworkPolicy. -- Antrea NetworkPolicy only supports stand-alone selectors. i.e. no support for - ClusterGroup references. -- Antrea NetworkPolicy does not support `namespaces` field within a peer, as ANP - themselves are scoped to a single Namespace. +- Antrea NetworkPolicy supports both stand-alone selectors and Group references. +- Antrea NetworkPolicy does not support `namespaces` field within a peer, as Antrea + NetworkPolicy themselves are scoped to a single Namespace. -### kubectl commands for Antrea NetworkPolicy +### Antrea NetworkPolicy with Group reference -The following kubectl commands can be used to retrieve ANP resources: +Groups can be referenced in `appliedTo` and `to`/`from`. Refer to the [Group](#group) +section for detailed information. + +The following example Antrea NetworkPolicy realizes the same network policy as the +[previous example](#the-antrea-networkpolicy-resource). It refers to three separately +defined Groups - "test-grp-with-db-selector" that selects all Pods labeled "role: db", +"test-grp-with-frontend-selector" that selects all Pods labeled "role: frontend" and +Pods labeled "role: nondb" in Namespaces labeled "role: db", "test-grp-with-ip-block" +that selects `ipblock` "10.0.10.0/24". + +```yaml +apiVersion: crd.antrea.io/v1alpha1 +kind: NetworkPolicy +metadata: + name: anp-with-groups + namespace: default +spec: + priority: 5 + tier: securityops + appliedTo: + - group: "test-grp-with-db-selector" + ingress: + - action: Allow + from: + - group: "test-grp-with-frontend-selector" + ports: + - protocol: TCP + port: 8080 + endPort: 9000 + name: AllowFromFrontend + enableLogging: false + egress: + - action: Drop + to: + - group: "test-grp-with-ip-block" + ports: + - protocol: TCP + port: 5978 + name: DropToThirdParty + enableLogging: true +``` + +### *kubectl* commands for Antrea NetworkPolicy + +The following `kubectl` commands can be used to retrieve ANP resources: ```bash # Use long name with API Group @@ -1404,7 +1452,7 @@ kind: ClusterGroup metadata: name: test-cg-ip-block spec: - # IPBlocks cannot be set along with PodSelector, NamespaceSelector or serviceReference. + # ipBlocks cannot be set along with podSelector, namespaceSelector or serviceReference. ipBlocks: - cidr: 10.0.10.0/24 --- @@ -1413,7 +1461,7 @@ kind: ClusterGroup metadata: name: test-cg-svc-ref spec: - # ServiceReference cannot be set along with PodSelector, NamespaceSelector or ipBlocks. + # serviceReference cannot be set along with podSelector, namespaceSelector or ipBlocks. serviceReference: name: test-service namespace: default @@ -1476,7 +1524,7 @@ cluster-wide group. Service. - **childGroups**: This selects existing ClusterGroups by name. The effective members - of the "parent" ClusterGrup will be the union of all its childGroups' members. + of the "parent" ClusterGroup will be the union of all its childGroups' members. See the section above for restrictions. **status**: The ClusterGroup `status` field determines the overall realization @@ -1486,9 +1534,9 @@ status of the group. when the controller has calculated all the corresponding workloads that match the selectors set in the group. -### kubectl commands for ClusterGroup +### *kubectl* commands for ClusterGroup -The following kubectl commands can be used to retrieve CG resources: +The following `kubectl` commands can be used to retrieve CG resources: ```bash # Use long name with API Group @@ -1501,6 +1549,108 @@ The following kubectl commands can be used to retrieve CG resources: kubectl get cg.crd.antrea.io ``` +## Group + +A Group CRD represents a different way for specifying how workloads are grouped +together, and is conceptually similar to the ClusterGroup CRD. Users will be able +to refer to Groups in Antrea NetworkPolicy resources instead of specifying Pod and +Namespace selectors every time. + +### Group CRD + +Below are some example Group specs: + +```yaml +# Group that selects all Pods labeled role: db in the default Namespace +apiVersion: crd.antrea.io/v1alpha3 +kind: Group +metadata: + name: test-grp-sel + namespace: default +spec: + podSelector: + matchLabels: + role: db +--- +# Group that selects all Pods labeled role: db in Namespaces labeled env: prod. +# This Group cannot be used in Antrea NetworkPolicy appliedTo because of the namespaceSelector. +apiVersion: crd.antrea.io/v1alpha3 +kind: Group +metadata: + name: test-grp-with-namespace +spec: + podSelector: + matchLabels: + role: db + namespaceSelector: + matchLabels: + env: prod +--- +# Group that selects IP block 10.0.10.0/24. +apiVersion: crd.antrea.io/v1alpha3 +kind: Group +metadata: + name: test-grp-ip-block +spec: + # ipBlocks cannot be set along with podSelector, namespaceSelector or serviceReference. + ipBlocks: + - cidr: 10.0.10.0/24 +--- +# Group that selects Service named test-service in the default Namespace. +apiVersion: crd.antrea.io/v1alpha3 +kind: Group +metadata: + name: test-grp-svc-ref +spec: + # serviceReference cannot be set along with podSelector, namespaceSelector or ipBlocks. + serviceReference: + name: test-service + namespace: default +--- +# Group that includes the previous Groups as childGroups. +apiVersion: crd.antrea.io/v1alpha3 +kind: Group +metadata: + name: test-grp-nested +spec: + childGroups: [test-grp-sel, test-grp-ip-blocks, test-grp-svc-ref] +``` + +### Restrictions and Key differences from ClusterGroup + +Group has a similar spec with ClusterGroup. However, there are key differences and +restrictions. + +- A Group can be set in an Antrea NetworkPolicy's `appliedTo` and `to`/`from` peers. + When set in the `appliedTo` field, it cannot include `namespaceSelector`, since + Antrea NetworkPolicy is Namespace scoped. For example, the + `test-grp-with-namespace` Group in the [sample](#group-crd) cannot be + used by Antrea NetworkPolicy `appliedTo`. +- Antrea will not validate the referenced Group resources for the `appliedTo` convention; + if the convention is violated in the Antrea NetworkPolicy's `appliedTo` section + or for any of the rules' `appliedTo`, then Antrea will report a condition + `Realizable=False` in the NetworkPolicy status, the condition includes + `NetworkPolicyAppliedToUnsupportedGroup` reason and a detailed message. +- `childGroups` only accepts strings, and they will be considered as names of + the Groups and will be looked up in the policy's own Namespace. For example, if + child Group `child-0` exists in `ns-2`, it should not be added as a child Group for + `ns-1/parentGroup-0`. + +### *kubectl* commands for Group + +The following `kubectl` commands can be used to retrieve Group resources: + +```bash + # Use long name with API Group + kubectl get groups.crd.antrea.io + + # Use short name + kubectl get grp + + # Use short name with API Group + kubectl get grp.crd.antrea.io +``` + ## RBAC Antrea-native policy CRDs are meant for admins to manage the security of their diff --git a/pkg/apis/controlplane/helper.go b/pkg/apis/controlplane/helper.go index d229dbe4e4e..b708cda4487 100644 --- a/pkg/apis/controlplane/helper.go +++ b/pkg/apis/controlplane/helper.go @@ -22,3 +22,19 @@ func (r *NetworkPolicyReference) ToString() string { } return fmt.Sprintf("%s:%s/%s", r.Type, r.Namespace, r.Name) } + +func (r *GroupReference) ToGroupName() string { + if r.Namespace == "" { + return r.Name + } + return fmt.Sprintf("%s/%s", r.Namespace, r.Name) +} + +// ToTypedString returns the Group or ClusterGroup namespaced name as a string along with its type. +// Typed strings are typically used in log messages. +func (r *GroupReference) ToTypedString() string { + if r.Namespace == "" { + return fmt.Sprintf("ClusterGroup:%s", r.Name) + } + return fmt.Sprintf("Group:%s/%s", r.Namespace, r.Name) +} diff --git a/pkg/apis/controlplane/helper_test.go b/pkg/apis/controlplane/helper_test.go new file mode 100644 index 00000000000..796f854036b --- /dev/null +++ b/pkg/apis/controlplane/helper_test.go @@ -0,0 +1,116 @@ +// Copyright 2021 Antrea Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package controlplane + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGroupReferenceToString(t *testing.T) { + tests := []struct { + name string + inGroupRef *GroupReference + out string + }{ + { + name: "cg-ref", + inGroupRef: &GroupReference{ + Namespace: "", + Name: "cgA", + }, + out: "cgA", + }, + { + name: "g-ref", + inGroupRef: &GroupReference{ + Namespace: "nsA", + Name: "gA", + }, + out: "nsA/gA", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actualOut := tt.inGroupRef.ToGroupName() + assert.Equal(t, tt.out, actualOut) + }) + } +} + +func TestGroupReferenceToTypedString(t *testing.T) { + tests := []struct { + name string + inGroupRef *GroupReference + out string + }{ + { + name: "cg-ref", + inGroupRef: &GroupReference{ + Namespace: "", + Name: "cgA", + }, + out: "ClusterGroup:cgA", + }, + { + name: "g-ref", + inGroupRef: &GroupReference{ + Namespace: "nsA", + Name: "gA", + }, + out: "Group:nsA/gA", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actualOut := tt.inGroupRef.ToTypedString() + assert.Equal(t, tt.out, actualOut) + }) + } +} + +func TestNetworkPolicyReferenceToString(t *testing.T) { + tests := []struct { + name string + inNPRef *NetworkPolicyReference + out string + }{ + { + name: "acnp-ref", + inNPRef: &NetworkPolicyReference{ + Type: AntreaClusterNetworkPolicy, + Namespace: "", + Name: "acnpA", + }, + out: "AntreaClusterNetworkPolicy:acnpA", + }, + { + name: "anp-ref", + inNPRef: &NetworkPolicyReference{ + Type: AntreaNetworkPolicy, + Namespace: "nsA", + Name: "anpA", + }, + out: "AntreaNetworkPolicy:nsA/anpA", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actualOut := tt.inNPRef.ToString() + assert.Equal(t, tt.out, actualOut) + }) + } +} diff --git a/pkg/apis/crd/v1alpha1/types.go b/pkg/apis/crd/v1alpha1/types.go index 698c9642ef2..ff942b22bdf 100644 --- a/pkg/apis/crd/v1alpha1/types.go +++ b/pkg/apis/crd/v1alpha1/types.go @@ -327,6 +327,9 @@ type NetworkPolicySpec struct { // NetworkPolicyPhase defines the phase in which a NetworkPolicy is. type NetworkPolicyPhase string +// NetworkPolicyConditionType describes the condition types of NetworkPolicies. +type NetworkPolicyConditionType string + // These are the valid values for NetworkPolicyPhase. const ( // NetworkPolicyPending means the NetworkPolicy has been accepted by the system, but it has not been processed by Antrea. @@ -337,6 +340,30 @@ const ( NetworkPolicyRealized NetworkPolicyPhase = "Realized" ) +// These are valid conditions of a deployment. +const ( + // NetworkPolicyConditionRealizable means the condition stores information about + // various realizable conditions of the NetworkPolicy. + NetworkPolicyConditionRealizable NetworkPolicyConditionType = "Realizable" +) + +// NetworkPolicyCondition describes the state of a NetworkPolicy at a certain point. +type NetworkPolicyCondition struct { + // Type of StatefulSet condition. + Type NetworkPolicyConditionType `json:"type"` + // Status of the condition, one of True, False, Unknown. + Status metav1.ConditionStatus `json:"status"` + // Last time the condition transitioned from one status to another. + // +optional + LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"` + // The reason for the condition's last transition. + // +optional + Reason string `json:"reason,omitempty"` + // A human-readable message indicating details about the transition. + // +optional + Message string `json:"message,omitempty"` +} + // NetworkPolicyStatus represents information about the status of a NetworkPolicy. type NetworkPolicyStatus struct { // The phase of a NetworkPolicy is a simple, high-level summary of the NetworkPolicy's status. @@ -347,6 +374,8 @@ type NetworkPolicyStatus struct { CurrentNodesRealized int32 `json:"currentNodesRealized"` // The total number of nodes that should realize the NetworkPolicy. DesiredNodesRealized int32 `json:"desiredNodesRealized"` + // Represents the latest available observations of a NetworkPolicy current state. + Conditions []NetworkPolicyCondition `json:"conditions"` } // Rule describes the traffic allowed to/from the workloads selected by diff --git a/pkg/apis/crd/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/crd/v1alpha1/zz_generated.deepcopy.go index 93d6d20dafa..5c1eff1343f 100644 --- a/pkg/apis/crd/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/crd/v1alpha1/zz_generated.deepcopy.go @@ -32,7 +32,7 @@ func (in *ClusterNetworkPolicy) DeepCopyInto(out *ClusterNetworkPolicy) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) - out.Status = in.Status + in.Status.DeepCopyInto(&out.Status) return } @@ -278,7 +278,7 @@ func (in *NetworkPolicy) DeepCopyInto(out *NetworkPolicy) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) - out.Status = in.Status + in.Status.DeepCopyInto(&out.Status) return } @@ -300,6 +300,23 @@ func (in *NetworkPolicy) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NetworkPolicyCondition) DeepCopyInto(out *NetworkPolicyCondition) { + *out = *in + in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkPolicyCondition. +func (in *NetworkPolicyCondition) DeepCopy() *NetworkPolicyCondition { + if in == nil { + return nil + } + out := new(NetworkPolicyCondition) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NetworkPolicyList) DeepCopyInto(out *NetworkPolicyList) { *out = *in @@ -486,6 +503,13 @@ func (in *NetworkPolicySpec) DeepCopy() *NetworkPolicySpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NetworkPolicyStatus) DeepCopyInto(out *NetworkPolicyStatus) { *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]NetworkPolicyCondition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } diff --git a/pkg/apis/crd/v1alpha3/register.go b/pkg/apis/crd/v1alpha3/register.go index f5fddd90c4e..a163d967408 100644 --- a/pkg/apis/crd/v1alpha3/register.go +++ b/pkg/apis/crd/v1alpha3/register.go @@ -46,6 +46,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &ClusterGroup{}, &ClusterGroupList{}, + &Group{}, + &GroupList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) diff --git a/pkg/apis/crd/v1alpha3/types.go b/pkg/apis/crd/v1alpha3/types.go index 4913d42b66d..e6c73f8253b 100644 --- a/pkg/apis/crd/v1alpha3/types.go +++ b/pkg/apis/crd/v1alpha3/types.go @@ -99,3 +99,30 @@ type ClusterGroupList struct { Items []ClusterGroup `json:"items,omitempty"` } + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Group can be used in AntreaNetworkPolicies. When used with AppliedTo, it cannot include NamespaceSelector, +// otherwise, Antrea will not realize the NetworkPolicy or rule, but will just update the NetworkPolicy +// Status as "Unrealizable". +type Group struct { + metav1.TypeMeta `json:",inline"` + // Standard metadata of the object. + metav1.ObjectMeta `json:"metadata,omitempty"` + + // Desired state of the group. + Spec GroupSpec `json:"spec"` + // Most recently observed status of the group. + Status GroupStatus `json:"status"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +type GroupList struct { + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ListMeta `json:"metadata,omitempty"` + + Items []Group `json:"items,omitempty"` +} diff --git a/pkg/apis/crd/v1alpha3/zz_generated.deepcopy.go b/pkg/apis/crd/v1alpha3/zz_generated.deepcopy.go index 67fe1bd3b75..6b37b1db00a 100644 --- a/pkg/apis/crd/v1alpha3/zz_generated.deepcopy.go +++ b/pkg/apis/crd/v1alpha3/zz_generated.deepcopy.go @@ -86,6 +86,34 @@ func (in *ClusterGroupList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Group) DeepCopyInto(out *Group) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Group. +func (in *Group) DeepCopy() *Group { + if in == nil { + return nil + } + out := new(Group) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Group) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GroupCondition) DeepCopyInto(out *GroupCondition) { *out = *in @@ -103,6 +131,39 @@ func (in *GroupCondition) DeepCopy() *GroupCondition { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GroupList) DeepCopyInto(out *GroupList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Group, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GroupList. +func (in *GroupList) DeepCopy() *GroupList { + if in == nil { + return nil + } + out := new(GroupList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GroupList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GroupSpec) DeepCopyInto(out *GroupSpec) { *out = *in diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index f97a3b9b333..f1c3c2b6839 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -276,6 +276,7 @@ func installHandlers(c *ExtraConfig, s *genericapiserver.GenericAPIServer) { s.Handler.NonGoRestfulMux.HandleFunc("/validate/acnp", webhook.HandlerForValidateFunc(v.Validate)) s.Handler.NonGoRestfulMux.HandleFunc("/validate/anp", webhook.HandlerForValidateFunc(v.Validate)) s.Handler.NonGoRestfulMux.HandleFunc("/validate/clustergroup", webhook.HandlerForValidateFunc(v.Validate)) + s.Handler.NonGoRestfulMux.HandleFunc("/validate/group", webhook.HandlerForValidateFunc(v.Validate)) // Install handlers for CRD conversion between versions s.Handler.NonGoRestfulMux.HandleFunc("/convert/clustergroup", webhook.HandleCRDConversion(controllernetworkpolicy.ConvertClusterGroupCRD)) diff --git a/pkg/apiserver/registry/networkpolicy/groupassociation/rest.go b/pkg/apiserver/registry/networkpolicy/groupassociation/rest.go index 307e285be4b..f028834ec31 100644 --- a/pkg/apiserver/registry/networkpolicy/groupassociation/rest.go +++ b/pkg/apiserver/registry/networkpolicy/groupassociation/rest.go @@ -63,7 +63,7 @@ func (r *REST) Get(ctx context.Context, name string, options *metav1.GetOptions) items := make([]controlplane.GroupReference, 0, len(groups)) for i := range groups { item := controlplane.GroupReference{ - Name: groups[i].Name, + Name: groups[i].SourceReference.Name, UID: groups[i].UID, } items = append(items, item) diff --git a/pkg/apiserver/registry/networkpolicy/groupassociation/rest_test.go b/pkg/apiserver/registry/networkpolicy/groupassociation/rest_test.go index 57879223c50..2a77300dffb 100644 --- a/pkg/apiserver/registry/networkpolicy/groupassociation/rest_test.go +++ b/pkg/apiserver/registry/networkpolicy/groupassociation/rest_test.go @@ -44,18 +44,27 @@ func TestRESTGet(t *testing.T) { groups := map[string][]antreatypes.Group{ "default/podA": { { - UID: "groupUID1", - Name: "cg1", + UID: "groupUID1", + SourceReference: &controlplane.GroupReference{ + Name: "cg1", + UID: "groupUID1", + }, }, }, "default/podB": { { - UID: "groupUID2", - Name: "cg2", + UID: "groupUID2", + SourceReference: &controlplane.GroupReference{ + Name: "cg2", + UID: "groupUID2", + }, }, { - UID: "groupUID3", - Name: "cg3", + UID: "groupUID3", + SourceReference: &controlplane.GroupReference{ + Name: "cg3", + UID: "groupUID3", + }, }, }, } diff --git a/pkg/client/clientset/versioned/typed/crd/v1alpha3/crd_client.go b/pkg/client/clientset/versioned/typed/crd/v1alpha3/crd_client.go index 4a696db8938..5819e722649 100644 --- a/pkg/client/clientset/versioned/typed/crd/v1alpha3/crd_client.go +++ b/pkg/client/clientset/versioned/typed/crd/v1alpha3/crd_client.go @@ -27,6 +27,7 @@ import ( type CrdV1alpha3Interface interface { RESTClient() rest.Interface ClusterGroupsGetter + GroupsGetter } // CrdV1alpha3Client is used to interact with features provided by the crd.antrea.io group. @@ -38,6 +39,10 @@ func (c *CrdV1alpha3Client) ClusterGroups() ClusterGroupInterface { return newClusterGroups(c) } +func (c *CrdV1alpha3Client) Groups(namespace string) GroupInterface { + return newGroups(c, namespace) +} + // NewForConfig creates a new CrdV1alpha3Client for the given config. // NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), // where httpClient was generated with rest.HTTPClientFor(c). diff --git a/pkg/client/clientset/versioned/typed/crd/v1alpha3/fake/fake_crd_client.go b/pkg/client/clientset/versioned/typed/crd/v1alpha3/fake/fake_crd_client.go index 3d3f1b461f3..5f2f3c9e3aa 100644 --- a/pkg/client/clientset/versioned/typed/crd/v1alpha3/fake/fake_crd_client.go +++ b/pkg/client/clientset/versioned/typed/crd/v1alpha3/fake/fake_crd_client.go @@ -30,6 +30,10 @@ func (c *FakeCrdV1alpha3) ClusterGroups() v1alpha3.ClusterGroupInterface { return &FakeClusterGroups{c} } +func (c *FakeCrdV1alpha3) Groups(namespace string) v1alpha3.GroupInterface { + return &FakeGroups{c, namespace} +} + // RESTClient returns a RESTClient that is used to communicate // with API server by this client implementation. func (c *FakeCrdV1alpha3) RESTClient() rest.Interface { diff --git a/pkg/client/clientset/versioned/typed/crd/v1alpha3/fake/fake_group.go b/pkg/client/clientset/versioned/typed/crd/v1alpha3/fake/fake_group.go new file mode 100644 index 00000000000..2d82e25e0d9 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/crd/v1alpha3/fake/fake_group.go @@ -0,0 +1,140 @@ +// Copyright 2022 Antrea Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha3 "antrea.io/antrea/pkg/apis/crd/v1alpha3" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeGroups implements GroupInterface +type FakeGroups struct { + Fake *FakeCrdV1alpha3 + ns string +} + +var groupsResource = schema.GroupVersionResource{Group: "crd.antrea.io", Version: "v1alpha3", Resource: "groups"} + +var groupsKind = schema.GroupVersionKind{Group: "crd.antrea.io", Version: "v1alpha3", Kind: "Group"} + +// Get takes name of the group, and returns the corresponding group object, and an error if there is any. +func (c *FakeGroups) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha3.Group, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(groupsResource, c.ns, name), &v1alpha3.Group{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha3.Group), err +} + +// List takes label and field selectors, and returns the list of Groups that match those selectors. +func (c *FakeGroups) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha3.GroupList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(groupsResource, groupsKind, c.ns, opts), &v1alpha3.GroupList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha3.GroupList{ListMeta: obj.(*v1alpha3.GroupList).ListMeta} + for _, item := range obj.(*v1alpha3.GroupList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested groups. +func (c *FakeGroups) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(groupsResource, c.ns, opts)) + +} + +// Create takes the representation of a group and creates it. Returns the server's representation of the group, and an error, if there is any. +func (c *FakeGroups) Create(ctx context.Context, group *v1alpha3.Group, opts v1.CreateOptions) (result *v1alpha3.Group, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(groupsResource, c.ns, group), &v1alpha3.Group{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha3.Group), err +} + +// Update takes the representation of a group and updates it. Returns the server's representation of the group, and an error, if there is any. +func (c *FakeGroups) Update(ctx context.Context, group *v1alpha3.Group, opts v1.UpdateOptions) (result *v1alpha3.Group, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(groupsResource, c.ns, group), &v1alpha3.Group{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha3.Group), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeGroups) UpdateStatus(ctx context.Context, group *v1alpha3.Group, opts v1.UpdateOptions) (*v1alpha3.Group, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(groupsResource, "status", c.ns, group), &v1alpha3.Group{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha3.Group), err +} + +// Delete takes name of the group and deletes it. Returns an error if one occurs. +func (c *FakeGroups) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(groupsResource, c.ns, name, opts), &v1alpha3.Group{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeGroups) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(groupsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha3.GroupList{}) + return err +} + +// Patch applies the patch and returns the patched group. +func (c *FakeGroups) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha3.Group, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(groupsResource, c.ns, name, pt, data, subresources...), &v1alpha3.Group{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha3.Group), err +} diff --git a/pkg/client/clientset/versioned/typed/crd/v1alpha3/generated_expansion.go b/pkg/client/clientset/versioned/typed/crd/v1alpha3/generated_expansion.go index c0bcbbf4871..45cd1122756 100644 --- a/pkg/client/clientset/versioned/typed/crd/v1alpha3/generated_expansion.go +++ b/pkg/client/clientset/versioned/typed/crd/v1alpha3/generated_expansion.go @@ -17,3 +17,5 @@ package v1alpha3 type ClusterGroupExpansion interface{} + +type GroupExpansion interface{} diff --git a/pkg/client/clientset/versioned/typed/crd/v1alpha3/group.go b/pkg/client/clientset/versioned/typed/crd/v1alpha3/group.go new file mode 100644 index 00000000000..dbf44ea4ba0 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/crd/v1alpha3/group.go @@ -0,0 +1,193 @@ +// Copyright 2021 Antrea Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha3 + +import ( + "context" + "time" + + v1alpha3 "antrea.io/antrea/pkg/apis/crd/v1alpha3" + scheme "antrea.io/antrea/pkg/client/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// GroupsGetter has a method to return a GroupInterface. +// A group's client should implement this interface. +type GroupsGetter interface { + Groups(namespace string) GroupInterface +} + +// GroupInterface has methods to work with Group resources. +type GroupInterface interface { + Create(ctx context.Context, group *v1alpha3.Group, opts v1.CreateOptions) (*v1alpha3.Group, error) + Update(ctx context.Context, group *v1alpha3.Group, opts v1.UpdateOptions) (*v1alpha3.Group, error) + UpdateStatus(ctx context.Context, group *v1alpha3.Group, opts v1.UpdateOptions) (*v1alpha3.Group, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha3.Group, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha3.GroupList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha3.Group, err error) + GroupExpansion +} + +// groups implements GroupInterface +type groups struct { + client rest.Interface + ns string +} + +// newGroups returns a Groups +func newGroups(c *CrdV1alpha3Client, namespace string) *groups { + return &groups{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the group, and returns the corresponding group object, and an error if there is any. +func (c *groups) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha3.Group, err error) { + result = &v1alpha3.Group{} + err = c.client.Get(). + Namespace(c.ns). + Resource("groups"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Groups that match those selectors. +func (c *groups) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha3.GroupList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha3.GroupList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("groups"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested groups. +func (c *groups) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("groups"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a group and creates it. Returns the server's representation of the group, and an error, if there is any. +func (c *groups) Create(ctx context.Context, group *v1alpha3.Group, opts v1.CreateOptions) (result *v1alpha3.Group, err error) { + result = &v1alpha3.Group{} + err = c.client.Post(). + Namespace(c.ns). + Resource("groups"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(group). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a group and updates it. Returns the server's representation of the group, and an error, if there is any. +func (c *groups) Update(ctx context.Context, group *v1alpha3.Group, opts v1.UpdateOptions) (result *v1alpha3.Group, err error) { + result = &v1alpha3.Group{} + err = c.client.Put(). + Namespace(c.ns). + Resource("groups"). + Name(group.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(group). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *groups) UpdateStatus(ctx context.Context, group *v1alpha3.Group, opts v1.UpdateOptions) (result *v1alpha3.Group, err error) { + result = &v1alpha3.Group{} + err = c.client.Put(). + Namespace(c.ns). + Resource("groups"). + Name(group.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(group). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the group and deletes it. Returns an error if one occurs. +func (c *groups) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("groups"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *groups) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("groups"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched group. +func (c *groups) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha3.Group, err error) { + result = &v1alpha3.Group{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("groups"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/informers/externalversions/crd/v1alpha3/group.go b/pkg/client/informers/externalversions/crd/v1alpha3/group.go new file mode 100644 index 00000000000..408eb68b96f --- /dev/null +++ b/pkg/client/informers/externalversions/crd/v1alpha3/group.go @@ -0,0 +1,88 @@ +// Copyright 2021 Antrea Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha3 + +import ( + "context" + time "time" + + crdv1alpha3 "antrea.io/antrea/pkg/apis/crd/v1alpha3" + versioned "antrea.io/antrea/pkg/client/clientset/versioned" + internalinterfaces "antrea.io/antrea/pkg/client/informers/externalversions/internalinterfaces" + v1alpha3 "antrea.io/antrea/pkg/client/listers/crd/v1alpha3" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// GroupInformer provides access to a shared informer and lister for +// Groups. +type GroupInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha3.GroupLister +} + +type groupInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewGroupInformer constructs a new informer for Group type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewGroupInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredGroupInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredGroupInformer constructs a new informer for Group type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredGroupInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.CrdV1alpha3().Groups(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.CrdV1alpha3().Groups(namespace).Watch(context.TODO(), options) + }, + }, + &crdv1alpha3.Group{}, + resyncPeriod, + indexers, + ) +} + +func (f *groupInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredGroupInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *groupInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&crdv1alpha3.Group{}, f.defaultInformer) +} + +func (f *groupInformer) Lister() v1alpha3.GroupLister { + return v1alpha3.NewGroupLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/externalversions/crd/v1alpha3/interface.go b/pkg/client/informers/externalversions/crd/v1alpha3/interface.go index aa4f24f5aae..df7fd513356 100644 --- a/pkg/client/informers/externalversions/crd/v1alpha3/interface.go +++ b/pkg/client/informers/externalversions/crd/v1alpha3/interface.go @@ -24,6 +24,8 @@ import ( type Interface interface { // ClusterGroups returns a ClusterGroupInformer. ClusterGroups() ClusterGroupInformer + // Groups returns a GroupInformer. + Groups() GroupInformer } type version struct { @@ -41,3 +43,8 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList func (v *version) ClusterGroups() ClusterGroupInformer { return &clusterGroupInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} } + +// Groups returns a GroupInformer. +func (v *version) Groups() GroupInformer { + return &groupInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} diff --git a/pkg/client/informers/externalversions/generic.go b/pkg/client/informers/externalversions/generic.go index 0378c370c15..13d0a999c82 100644 --- a/pkg/client/informers/externalversions/generic.go +++ b/pkg/client/informers/externalversions/generic.go @@ -80,6 +80,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource // Group=crd.antrea.io, Version=v1alpha3 case v1alpha3.SchemeGroupVersion.WithResource("clustergroups"): return &genericInformer{resource: resource.GroupResource(), informer: f.Crd().V1alpha3().ClusterGroups().Informer()}, nil + case v1alpha3.SchemeGroupVersion.WithResource("groups"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Crd().V1alpha3().Groups().Informer()}, nil // Group=crd.antrea.io, Version=v1beta1 case v1beta1.SchemeGroupVersion.WithResource("antreaagentinfos"): diff --git a/pkg/client/listers/crd/v1alpha3/expansion_generated.go b/pkg/client/listers/crd/v1alpha3/expansion_generated.go index cb87c45ce99..78a61a92d26 100644 --- a/pkg/client/listers/crd/v1alpha3/expansion_generated.go +++ b/pkg/client/listers/crd/v1alpha3/expansion_generated.go @@ -19,3 +19,11 @@ package v1alpha3 // ClusterGroupListerExpansion allows custom methods to be added to // ClusterGroupLister. type ClusterGroupListerExpansion interface{} + +// GroupListerExpansion allows custom methods to be added to +// GroupLister. +type GroupListerExpansion interface{} + +// GroupNamespaceListerExpansion allows custom methods to be added to +// GroupNamespaceLister. +type GroupNamespaceListerExpansion interface{} diff --git a/pkg/client/listers/crd/v1alpha3/group.go b/pkg/client/listers/crd/v1alpha3/group.go new file mode 100644 index 00000000000..45539b7b46a --- /dev/null +++ b/pkg/client/listers/crd/v1alpha3/group.go @@ -0,0 +1,97 @@ +// Copyright 2021 Antrea Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha3 + +import ( + v1alpha3 "antrea.io/antrea/pkg/apis/crd/v1alpha3" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// GroupLister helps list Groups. +// All objects returned here must be treated as read-only. +type GroupLister interface { + // List lists all Groups in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha3.Group, err error) + // Groups returns an object that can list and get Groups. + Groups(namespace string) GroupNamespaceLister + GroupListerExpansion +} + +// groupLister implements the GroupLister interface. +type groupLister struct { + indexer cache.Indexer +} + +// NewGroupLister returns a new GroupLister. +func NewGroupLister(indexer cache.Indexer) GroupLister { + return &groupLister{indexer: indexer} +} + +// List lists all Groups in the indexer. +func (s *groupLister) List(selector labels.Selector) (ret []*v1alpha3.Group, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha3.Group)) + }) + return ret, err +} + +// Groups returns an object that can list and get Groups. +func (s *groupLister) Groups(namespace string) GroupNamespaceLister { + return groupNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// GroupNamespaceLister helps list and get Groups. +// All objects returned here must be treated as read-only. +type GroupNamespaceLister interface { + // List lists all Groups in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha3.Group, err error) + // Get retrieves the Group from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha3.Group, error) + GroupNamespaceListerExpansion +} + +// groupNamespaceLister implements the GroupNamespaceLister +// interface. +type groupNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all Groups in the indexer for a given namespace. +func (s groupNamespaceLister) List(selector labels.Selector) (ret []*v1alpha3.Group, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha3.Group)) + }) + return ret, err +} + +// Get retrieves the Group from the indexer for a given namespace and name. +func (s groupNamespaceLister) Get(name string) (*v1alpha3.Group, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha3.Resource("group"), name) + } + return obj.(*v1alpha3.Group), nil +} diff --git a/pkg/controller/grouping/group_entity_index.go b/pkg/controller/grouping/group_entity_index.go index 2bb055ecdb3..61c3ba14f5c 100644 --- a/pkg/controller/grouping/group_entity_index.go +++ b/pkg/controller/grouping/group_entity_index.go @@ -672,7 +672,7 @@ func (i *GroupEntityIndex) Run(stopCh <-chan struct{}) { klog.Info("Stopping GroupEntityIndex") return case group := <-i.eventChan: - parts := strings.Split(group, "/") + parts := strings.SplitN(group, "/", 2) groupType, name := GroupType(parts[0]), parts[1] for _, handler := range i.eventHandlers[groupType] { handler(name) diff --git a/pkg/controller/networkpolicy/antreanetworkpolicy.go b/pkg/controller/networkpolicy/antreanetworkpolicy.go index 4dfac5c3978..e8b80597a25 100644 --- a/pkg/controller/networkpolicy/antreanetworkpolicy.go +++ b/pkg/controller/networkpolicy/antreanetworkpolicy.go @@ -15,6 +15,8 @@ package networkpolicy import ( + "fmt" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/tools/cache" "k8s.io/klog/v2" @@ -129,22 +131,34 @@ func (n *NetworkPolicyController) processAntreaNetworkPolicy(np *crdv1alpha1.Net // either in the spec section or in ingress/egress rules. // The span calculation and stale appliedToGroup cleanup logic would work seamlessly for both cases. appliedToGroupNamesSet := sets.String{} + rules := make([]controlplane.NetworkPolicyRule, 0, len(np.Spec.Ingress)+len(np.Spec.Egress)) + newUnrealizableInternalNetworkPolicy := func(err error) *antreatypes.NetworkPolicy { + return &antreatypes.NetworkPolicy{ + SourceRef: &controlplane.NetworkPolicyReference{ + Type: controlplane.AntreaNetworkPolicy, + Namespace: np.Namespace, + Name: np.Name, + UID: np.UID, + }, + Name: internalNetworkPolicyKeyFunc(np), + UID: np.UID, + Generation: np.Generation, + RealizationError: err, + } + } // Create AppliedToGroup for each AppliedTo present in AntreaNetworkPolicy spec. - for _, at := range np.Spec.AppliedTo { - appliedToGroupNamesSet.Insert(n.createAppliedToGroup( - np.Namespace, at.PodSelector, at.NamespaceSelector, at.ExternalEntitySelector)) + _, err := n.processAppliedTo(np.Namespace, np.Spec.AppliedTo, appliedToGroupNamesSet) + if err != nil { + return newUnrealizableInternalNetworkPolicy(err) } - rules := make([]controlplane.NetworkPolicyRule, 0, len(np.Spec.Ingress)+len(np.Spec.Egress)) // Compute NetworkPolicyRule for Ingress Rule. for idx, ingressRule := range np.Spec.Ingress { // Set default action to ALLOW to allow traffic. services, namedPortExists := toAntreaServicesForCRD(ingressRule.Ports, ingressRule.Protocols) - var appliedToGroupNamesForRule []string // Create AppliedToGroup for each AppliedTo present in the ingress rule. - for _, at := range ingressRule.AppliedTo { - atGroup := n.createAppliedToGroup(np.Namespace, at.PodSelector, at.NamespaceSelector, at.ExternalEntitySelector) - appliedToGroupNamesForRule = append(appliedToGroupNamesForRule, atGroup) - appliedToGroupNamesSet.Insert(atGroup) + atGroups, err := n.processAppliedTo(np.Namespace, ingressRule.AppliedTo, appliedToGroupNamesSet) + if err != nil { + return newUnrealizableInternalNetworkPolicy(err) } rules = append(rules, controlplane.NetworkPolicyRule{ Direction: controlplane.DirectionIn, @@ -154,19 +168,17 @@ func (n *NetworkPolicyController) processAntreaNetworkPolicy(np *crdv1alpha1.Net Action: ingressRule.Action, Priority: int32(idx), EnableLogging: ingressRule.EnableLogging, - AppliedToGroups: appliedToGroupNamesForRule, + AppliedToGroups: atGroups, }) } // Compute NetworkPolicyRule for Egress Rule. for idx, egressRule := range np.Spec.Egress { // Set default action to ALLOW to allow traffic. services, namedPortExists := toAntreaServicesForCRD(egressRule.Ports, egressRule.Protocols) - var appliedToGroupNamesForRule []string - // Create AppliedToGroup for each AppliedTo present in the ingress rule. - for _, at := range egressRule.AppliedTo { - atGroup := n.createAppliedToGroup(np.Namespace, at.PodSelector, at.NamespaceSelector, at.ExternalEntitySelector) - appliedToGroupNamesForRule = append(appliedToGroupNamesForRule, atGroup) - appliedToGroupNamesSet.Insert(atGroup) + // Create AppliedToGroup for each AppliedTo present in the egress rule. + atGroups, err := n.processAppliedTo(np.Namespace, egressRule.AppliedTo, appliedToGroupNamesSet) + if err != nil { + return newUnrealizableInternalNetworkPolicy(err) } var peers *controlplane.NetworkPolicyPeer if egressRule.ToServices != nil { @@ -182,7 +194,7 @@ func (n *NetworkPolicyController) processAntreaNetworkPolicy(np *crdv1alpha1.Net Action: egressRule.Action, Priority: int32(idx), EnableLogging: egressRule.EnableLogging, - AppliedToGroups: appliedToGroupNamesForRule, + AppliedToGroups: atGroups, }) } tierPriority := n.getTierPriority(np.Spec.Tier) @@ -204,3 +216,59 @@ func (n *NetworkPolicyController) processAntreaNetworkPolicy(np *crdv1alpha1.Net } return internalNetworkPolicy } + +func (n *NetworkPolicyController) processAppliedTo(namespace string, appliedTo []crdv1alpha1.NetworkPolicyPeer, appliedToGroupNamesSet sets.String) ([]string, error) { + var appliedToGroupNames []string + for _, at := range appliedTo { + var atg string + if at.Group != "" { + var err error + atg, err = n.processAppliedToGroupForNamespacedGroup(namespace, at.Group) + if err != nil { + return appliedToGroupNames, err + } + } else { + atg = n.createAppliedToGroup(namespace, at.PodSelector, at.NamespaceSelector, at.ExternalEntitySelector) + } + if atg != "" { + appliedToGroupNames = append(appliedToGroupNames, atg) + appliedToGroupNamesSet.Insert(atg) + } + } + return appliedToGroupNames, nil +} + +// ErrNetworkPolicyAppliedToUnsupportedGroup is an error response when +// a Group with IPBlocks or NamespaceSelector is used as AppliedTo. +type ErrNetworkPolicyAppliedToUnsupportedGroup struct { + namespace string + groupName string +} + +func (e ErrNetworkPolicyAppliedToUnsupportedGroup) Error() string { + return fmt.Sprintf("group %s/%s with IPBlocks or NamespaceSelector can not be used as AppliedTo", e.namespace, e.groupName) +} + +func (n *NetworkPolicyController) processAppliedToGroupForNamespacedGroup(namespace, groupName string) (string, error) { + // Retrieve Group for corresponding entry in the AppliedToGroup. + g, err := n.grpLister.Groups(namespace).Get(groupName) + if err != nil { + // The Group referred to has not been created yet. + return "", nil + } + key := internalGroupKeyFunc(g) + // Find the internal Group corresponding to this Group + ig, found, _ := n.internalGroupStore.Get(key) + if !found { + // Internal Group was not found. Once the internal Group is created, the sync + // worker for internal group will re-enqueue the ClusterNetworkPolicy processing + // which will trigger the creation of AddressGroup. + return "", nil + } + intGrp := ig.(*antreatypes.Group) + if len(intGrp.IPBlocks) > 0 || (intGrp.Selector != nil && intGrp.Selector.NamespaceSelector != nil) { + klog.V(2).InfoS("Group with IPBlocks or NamespaceSelector can not be used as AppliedTo", "Group", g) + return "", ErrNetworkPolicyAppliedToUnsupportedGroup{namespace: namespace, groupName: groupName} + } + return n.createAppliedToGroupForInternalGroup(intGrp), nil +} diff --git a/pkg/controller/networkpolicy/antreanetworkpolicy_test.go b/pkg/controller/networkpolicy/antreanetworkpolicy_test.go index b64517c9851..d36f18ae532 100644 --- a/pkg/controller/networkpolicy/antreanetworkpolicy_test.go +++ b/pkg/controller/networkpolicy/antreanetworkpolicy_test.go @@ -15,6 +15,7 @@ package networkpolicy import ( + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -24,6 +25,7 @@ import ( "antrea.io/antrea/pkg/apis/controlplane" crdv1alpha1 "antrea.io/antrea/pkg/apis/crd/v1alpha1" + crdv1alpha3 "antrea.io/antrea/pkg/apis/crd/v1alpha3" antreatypes "antrea.io/antrea/pkg/controller/types" ) @@ -752,6 +754,78 @@ func TestDeleteANP(t *testing.T) { assert.False(t, found, "expected internal NetworkPolicy to be deleted") } +func TestProcessAppliedToGroupsForGroup(t *testing.T) { + selectorA := metav1.LabelSelector{MatchLabels: map[string]string{"foo1": "bar1"}} + cidr := "10.0.0.0/24" + // gA with selector present in cache + gA := crdv1alpha3.Group{ + ObjectMeta: metav1.ObjectMeta{Namespace: "nsA", Name: "gA", UID: "uidA"}, + Spec: crdv1alpha3.GroupSpec{ + PodSelector: &selectorA, + }, + } + // gB with IPBlock present in cache + gB := crdv1alpha3.Group{ + ObjectMeta: metav1.ObjectMeta{Namespace: "nsB", Name: "gB", UID: "uidB"}, + Spec: crdv1alpha3.GroupSpec{ + IPBlocks: []crdv1alpha1.IPBlock{ + { + CIDR: cidr, + }, + }, + }, + } + // gC not found in cache + gC := crdv1alpha3.Group{ + ObjectMeta: metav1.ObjectMeta{Namespace: "nsC", Name: "gC", UID: "uidC"}, + Spec: crdv1alpha3.GroupSpec{ + NamespaceSelector: &selectorA, + }, + } + _, npc := newController() + npc.addGroup(&gA) + npc.addGroup(&gB) + npc.gStore.Add(&gA) + npc.gStore.Add(&gB) + tests := []struct { + name string + namespace string + inputG string + expectedAG string + }{ + { + name: "empty-grp-no-result", + namespace: "nsA", + inputG: "", + expectedAG: "", + }, + { + name: "ipblock-grp-no-result", + namespace: "nsB", + inputG: gB.Name, + expectedAG: "", + }, + { + name: "selector-grp-missing-no-result", + namespace: "nsC", + inputG: gC.Name, + expectedAG: "", + }, + { + name: "selector-grp", + namespace: "nsA", + inputG: gA.Name, + expectedAG: fmt.Sprintf("%s/%s", gA.Namespace, gA.Name), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actualAG, _ := npc.processAppliedToGroupForNamespacedGroup(tt.namespace, tt.inputG) + assert.Equal(t, tt.expectedAG, actualAG, "appliedToGroup list does not match") + }) + } +} + // util functions for testing. func getANP() *crdv1alpha1.NetworkPolicy { p10 := float64(10) diff --git a/pkg/controller/networkpolicy/clustergroup.go b/pkg/controller/networkpolicy/clustergroup.go index 52213b6dac8..e8b05849384 100644 --- a/pkg/controller/networkpolicy/clustergroup.go +++ b/pkg/controller/networkpolicy/clustergroup.go @@ -118,8 +118,8 @@ func (c *NetworkPolicyController) deleteClusterGroup(oldObj interface{}) { func (c *NetworkPolicyController) processClusterGroup(cg *crdv1alpha3.ClusterGroup) *antreatypes.Group { internalGroup := antreatypes.Group{ - Name: cg.Name, - UID: cg.UID, + SourceReference: getClusterGroupSourceRef(cg), + UID: cg.UID, } if len(cg.Spec.ChildGroups) > 0 { for _, childCGName := range cg.Spec.ChildGroups { @@ -197,29 +197,19 @@ func (c *NetworkPolicyController) processNextInternalGroupWorkItem() bool { return true } -func (c *NetworkPolicyController) syncInternalGroup(key string) error { - defer c.triggerCNPUpdates(key) - defer c.triggerParentGroupSync(key) - // Retrieve the internal Group corresponding to this key. - grpObj, found, _ := c.internalGroupStore.Get(key) - if !found { - klog.V(2).Infof("Internal group %s not found.", key) - c.groupingInterface.DeleteGroup(clusterGroupType, key) - return nil - } - grp := grpObj.(*antreatypes.Group) +func (c *NetworkPolicyController) syncInternalClusterGroup(grp *antreatypes.Group) error { originalMembersComputedStatus := grp.MembersComputed // Retrieve the ClusterGroup corresponding to this key. - cg, err := c.cgLister.Get(grp.Name) + cg, err := c.cgLister.Get(grp.SourceReference.ToGroupName()) if err != nil { - klog.Infof("Didn't find the ClusterGroup %s, skip processing of internal group", grp.Name) + klog.InfoS("Didn't find ClusterGroup, skip processing of internal group", "ClusterGroup", grp.SourceReference.ToTypedString()) return nil } selectorUpdated := c.processServiceReference(grp) if grp.Selector != nil { - c.groupingInterface.AddGroup(clusterGroupType, grp.Name, grp.Selector) + c.groupingInterface.AddGroup(internalGroupType, grp.SourceReference.ToGroupName(), grp.Selector) } else { - c.groupingInterface.DeleteGroup(clusterGroupType, grp.Name) + c.groupingInterface.DeleteGroup(internalGroupType, grp.SourceReference.ToGroupName()) } membersComputed, membersComputedStatus := true, v1.ConditionFalse @@ -238,8 +228,8 @@ func (c *NetworkPolicyController) syncInternalGroup(key string) error { } } if membersComputed { - klog.V(4).Infof("Updating GroupMembersComputed Status for group %s", cg.Name) - err = c.updateGroupStatus(cg, v1.ConditionTrue) + klog.V(4).InfoS("Updating GroupMembersComputed Status for ClusterGroup", "ClusterGroup", cg.Name) + err = c.updateClusterGroupStatus(cg, v1.ConditionTrue) if err != nil { klog.Errorf("Failed to update ClusterGroup %s GroupMembersComputed condition to %s: %v", cg.Name, v1.ConditionTrue, err) } else { @@ -250,19 +240,27 @@ func (c *NetworkPolicyController) syncInternalGroup(key string) error { // Update the internal Group object in the store with the new selector and status. updatedGrp := &antreatypes.Group{ UID: grp.UID, - Name: grp.Name, + SourceReference: grp.SourceReference, MembersComputed: membersComputedStatus, Selector: grp.Selector, IPBlocks: grp.IPBlocks, ServiceReference: grp.ServiceReference, ChildGroups: grp.ChildGroups, } - klog.V(2).Infof("Updating existing internal Group %s", key) + klog.V(2).InfoS("Updating existing internal Group", "internalGroup", grp.SourceReference.ToGroupName()) c.internalGroupStore.Update(updatedGrp) } return err } +func getClusterGroupSourceRef(cg *crdv1alpha3.ClusterGroup) *controlplane.GroupReference { + return &controlplane.GroupReference{ + Name: cg.GetName(), + Namespace: cg.GetNamespace(), + UID: cg.GetUID(), + } +} + func (c *NetworkPolicyController) triggerParentGroupSync(grp string) { // TODO: if the max supported group nesting level increases, a Group having children // will no longer be a valid indication that it cannot have parents. @@ -273,7 +271,7 @@ func (c *NetworkPolicyController) triggerParentGroupSync(grp string) { } for _, p := range parentGroupObjs { parentGrp := p.(*antreatypes.Group) - c.enqueueInternalGroup(parentGrp.Name) + c.enqueueInternalGroup(parentGrp.SourceReference.ToGroupName()) } } @@ -291,8 +289,8 @@ func (c *NetworkPolicyController) triggerCNPUpdates(cg string) { } } -// updateGroupStatus updates the Status subresource for a ClusterGroup. -func (c *NetworkPolicyController) updateGroupStatus(cg *crdv1alpha3.ClusterGroup, cStatus v1.ConditionStatus) error { +// updateClusterGroupStatus updates the Status subresource for a ClusterGroup. +func (c *NetworkPolicyController) updateClusterGroupStatus(cg *crdv1alpha3.ClusterGroup, cStatus v1.ConditionStatus) error { condStatus := crdv1alpha3.GroupCondition{ Status: cStatus, Type: crdv1alpha3.GroupMembersComputed, @@ -312,19 +310,6 @@ func (c *NetworkPolicyController) updateGroupStatus(cg *crdv1alpha3.ClusterGroup return err } -// groupMembersComputedConditionEqual checks whether the condition status for GroupMembersComputed condition -// is same. Returns true if equal, otherwise returns false. It disregards the lastTransitionTime field. -func groupMembersComputedConditionEqual(conds []crdv1alpha3.GroupCondition, condition crdv1alpha3.GroupCondition) bool { - for _, c := range conds { - if c.Type == crdv1alpha3.GroupMembersComputed { - if c.Status == condition.Status { - return true - } - } - } - return false -} - // processServiceReference knows how to process the serviceReference in the group, and set the group // selector based on the Service referenced. It returns true if the group's selector needs to be // updated after serviceReference processing, and false otherwise. @@ -336,7 +321,7 @@ func (c *NetworkPolicyController) processServiceReference(group *antreatypes.Gro originalSelectorName := getNormalizedNameForSelector(group.Selector) svc, err := c.serviceLister.Services(svcRef.Namespace).Get(svcRef.Name) if err != nil { - klog.V(2).Infof("Error getting Service object %s/%s: %v, setting empty selector for Group %s", svcRef.Namespace, svcRef.Name, err, group.Name) + klog.V(2).InfoS("Error getting Service object, setting empty selector for internal Group", "Service", svcRef.Namespace+"/"+svcRef.Name, "error", err, "internalGroup", group.SourceReference.ToTypedString()) group.Selector = nil return originalSelectorName == getNormalizedNameForSelector(nil) } @@ -371,7 +356,7 @@ func (c *NetworkPolicyController) GetAssociatedGroups(name, namespace string) ([ return nil, nil } } - clusterGroups, exists := groups[clusterGroupType] + clusterGroups, exists := groups[internalGroupType] if !exists { return nil, nil } @@ -382,8 +367,8 @@ func (c *NetworkPolicyController) GetAssociatedGroups(name, namespace string) ([ // Remove duplicates in the groupObj slice. groupKeys, j := make(map[string]bool), 0 for _, g := range groupObjs { - if _, exists := groupKeys[g.Name]; !exists { - groupKeys[g.Name] = true + if _, exists := groupKeys[g.SourceReference.ToGroupName()]; !exists { + groupKeys[g.SourceReference.ToGroupName()] = true groupObjs[j] = g j++ } @@ -401,9 +386,9 @@ func (c *NetworkPolicyController) getAssociatedGroupsByName(grpName string) []an } grp := groupObj.(*antreatypes.Group) groups = append(groups, *grp) - parentGroupObjs, err := c.internalGroupStore.GetByIndex(store.ChildGroupIndex, grp.Name) + parentGroupObjs, err := c.internalGroupStore.GetByIndex(store.ChildGroupIndex, grp.SourceReference.ToGroupName()) if err != nil { - klog.Errorf("Error retrieving parents of ClusterGroup %s: %v", grp.Name, err) + klog.Errorf("Error retrieving parents of %s: %v", grp.SourceReference.ToTypedString(), err) } for _, p := range parentGroupObjs { parentGrp := p.(*antreatypes.Group) @@ -419,7 +404,7 @@ func (c *NetworkPolicyController) GetGroupMembers(cgName string) (controlplane.G groupObj, found, _ := c.internalGroupStore.Get(cgName) if found { group := groupObj.(*antreatypes.Group) - member, ipb := c.getClusterGroupMembers(group) + member, ipb := c.getInternalGroupMembers(group) return member, ipb, nil } return nil, nil, fmt.Errorf("no internal Group with name %s is found", cgName) diff --git a/pkg/controller/networkpolicy/clustergroup_test.go b/pkg/controller/networkpolicy/clustergroup_test.go index 24c74b3257c..804fe41551c 100644 --- a/pkg/controller/networkpolicy/clustergroup_test.go +++ b/pkg/controller/networkpolicy/clustergroup_test.go @@ -53,8 +53,11 @@ func TestProcessClusterGroup(t *testing.T) { }, }, expectedGroup: &antreatypes.Group{ - UID: "uidA", - Name: "cgA", + UID: "uidA", + SourceReference: &controlplane.GroupReference{ + Name: "cgA", + UID: "uidA", + }, Selector: antreatypes.NewGroupSelector("", nil, &selectorA, nil, nil), }, }, @@ -67,8 +70,11 @@ func TestProcessClusterGroup(t *testing.T) { }, }, expectedGroup: &antreatypes.Group{ - UID: "uidB", - Name: "cgB", + UID: "uidB", + SourceReference: &controlplane.GroupReference{ + Name: "cgB", + UID: "uidB", + }, Selector: antreatypes.NewGroupSelector("", &selectorB, nil, nil, nil), }, }, @@ -82,8 +88,11 @@ func TestProcessClusterGroup(t *testing.T) { }, }, expectedGroup: &antreatypes.Group{ - UID: "uidC", - Name: "cgC", + UID: "uidC", + SourceReference: &controlplane.GroupReference{ + Name: "cgC", + UID: "uidC", + }, Selector: antreatypes.NewGroupSelector("", &selectorC, &selectorD, nil, nil), }, }, @@ -100,8 +109,11 @@ func TestProcessClusterGroup(t *testing.T) { }, }, expectedGroup: &antreatypes.Group{ - UID: "uidD", - Name: "cgD", + UID: "uidD", + SourceReference: &controlplane.GroupReference{ + Name: "cgD", + UID: "uidD", + }, IPBlocks: []controlplane.IPBlock{ { CIDR: *cidrIPNet, @@ -122,8 +134,11 @@ func TestProcessClusterGroup(t *testing.T) { }, }, expectedGroup: &antreatypes.Group{ - UID: "uidE", - Name: "cgE", + UID: "uidE", + SourceReference: &controlplane.GroupReference{ + Name: "cgE", + UID: "uidE", + }, ServiceReference: &controlplane.ServiceReference{ Name: "test-svc", Namespace: "test-ns", @@ -139,8 +154,11 @@ func TestProcessClusterGroup(t *testing.T) { }, }, expectedGroup: &antreatypes.Group{ - UID: "uidF", - Name: "cgF", + UID: "uidF", + SourceReference: &controlplane.GroupReference{ + Name: "cgF", + UID: "uidF", + }, ChildGroups: []string{"cgA", "cgB"}, }, }, @@ -175,8 +193,11 @@ func TestAddClusterGroup(t *testing.T) { }, }, expectedGroup: &antreatypes.Group{ - UID: "uidA", - Name: "cgA", + UID: "uidA", + SourceReference: &controlplane.GroupReference{ + Name: "cgA", + UID: "uidA", + }, Selector: antreatypes.NewGroupSelector("", nil, &selectorA, nil, nil), }, }, @@ -189,8 +210,11 @@ func TestAddClusterGroup(t *testing.T) { }, }, expectedGroup: &antreatypes.Group{ - UID: "uidB", - Name: "cgB", + UID: "uidB", + SourceReference: &controlplane.GroupReference{ + Name: "cgB", + UID: "uidB", + }, Selector: antreatypes.NewGroupSelector("", &selectorB, nil, nil, nil), }, }, @@ -204,8 +228,11 @@ func TestAddClusterGroup(t *testing.T) { }, }, expectedGroup: &antreatypes.Group{ - UID: "uidC", - Name: "cgC", + UID: "uidC", + SourceReference: &controlplane.GroupReference{ + Name: "cgC", + UID: "uidC", + }, Selector: antreatypes.NewGroupSelector("", &selectorC, &selectorD, nil, nil), }, }, @@ -222,8 +249,11 @@ func TestAddClusterGroup(t *testing.T) { }, }, expectedGroup: &antreatypes.Group{ - UID: "uidD", - Name: "cgD", + UID: "uidD", + SourceReference: &controlplane.GroupReference{ + Name: "cgD", + UID: "uidD", + }, IPBlocks: []controlplane.IPBlock{ { CIDR: *cidrIPNet, @@ -272,8 +302,11 @@ func TestUpdateClusterGroup(t *testing.T) { }, }, expectedGroup: &antreatypes.Group{ - UID: "uidA", - Name: "cgA", + UID: "uidA", + SourceReference: &controlplane.GroupReference{ + Name: "cgA", + UID: "uidA", + }, Selector: antreatypes.NewGroupSelector("", nil, &selectorB, nil, nil), }, }, @@ -286,8 +319,11 @@ func TestUpdateClusterGroup(t *testing.T) { }, }, expectedGroup: &antreatypes.Group{ - UID: "uidA", - Name: "cgA", + UID: "uidA", + SourceReference: &controlplane.GroupReference{ + Name: "cgA", + UID: "uidA", + }, Selector: antreatypes.NewGroupSelector("", &selectorC, nil, nil, nil), }, }, @@ -301,8 +337,11 @@ func TestUpdateClusterGroup(t *testing.T) { }, }, expectedGroup: &antreatypes.Group{ - UID: "uidA", - Name: "cgA", + UID: "uidA", + SourceReference: &controlplane.GroupReference{ + Name: "cgA", + UID: "uidA", + }, Selector: antreatypes.NewGroupSelector("", &selectorC, &selectorD, nil, nil), }, }, @@ -319,8 +358,11 @@ func TestUpdateClusterGroup(t *testing.T) { }, }, expectedGroup: &antreatypes.Group{ - UID: "uidA", - Name: "cgA", + UID: "uidA", + SourceReference: &controlplane.GroupReference{ + Name: "cgA", + UID: "uidA", + }, IPBlocks: []controlplane.IPBlock{ { CIDR: *cidrIPNet, @@ -341,8 +383,11 @@ func TestUpdateClusterGroup(t *testing.T) { }, }, expectedGroup: &antreatypes.Group{ - UID: "uidA", - Name: "cgA", + UID: "uidA", + SourceReference: &controlplane.GroupReference{ + Name: "cgA", + UID: "uidA", + }, ServiceReference: &controlplane.ServiceReference{ Name: "test-svc", Namespace: "test-ns", @@ -358,8 +403,11 @@ func TestUpdateClusterGroup(t *testing.T) { }, }, expectedGroup: &antreatypes.Group{ - UID: "uidA", - Name: "cgA", + UID: "uidA", + SourceReference: &controlplane.GroupReference{ + Name: "cgA", + UID: "uidA", + }, ChildGroups: []string{"cgB", "cgC"}, }, }, @@ -393,7 +441,7 @@ func TestDeleteCG(t *testing.T) { assert.False(t, found, "expected internal Group to be deleted") } -func TestGroupMembersComputedConditionEqual(t *testing.T) { +func TestClusterClusterGroupMembersComputedConditionEqual(t *testing.T) { tests := []struct { name string existingConds []crdv1alpha3.GroupCondition @@ -468,16 +516,22 @@ func TestFilterInternalGroupsForService(t *testing.T) { }, } grp1 := &antreatypes.Group{ - UID: "uid1", - Name: "cgA", + UID: "uid1", + SourceReference: &controlplane.GroupReference{ + Name: "cgA", + UID: "uid1", + }, ServiceReference: &controlplane.ServiceReference{ Name: "svc1", Namespace: metav1.NamespaceDefault, }, } grp2 := &antreatypes.Group{ - UID: "uid2", - Name: "cgB", + UID: "uid2", + SourceReference: &controlplane.GroupReference{ + Name: "cgB", + UID: "uid1", + }, ServiceReference: &controlplane.ServiceReference{ Name: "svc1", Namespace: metav1.NamespaceDefault, @@ -485,8 +539,11 @@ func TestFilterInternalGroupsForService(t *testing.T) { Selector: antreatypes.NewGroupSelector(metav1.NamespaceDefault, &selectorSpec, nil, nil, nil), } grp3 := &antreatypes.Group{ - UID: "uid3", - Name: "cgC", + UID: "uid3", + SourceReference: &controlplane.GroupReference{ + Name: "cgC", + UID: "uid3", + }, ServiceReference: &controlplane.ServiceReference{ Name: "svc2", Namespace: "test", @@ -495,8 +552,11 @@ func TestFilterInternalGroupsForService(t *testing.T) { Selector: antreatypes.NewGroupSelector("test", nil, nil, nil, nil), } grp4 := &antreatypes.Group{ - UID: "uid4", - Name: "cgD", + UID: "uid4", + SourceReference: &controlplane.GroupReference{ + Name: "cgD", + UID: "uid4", + }, ServiceReference: &controlplane.ServiceReference{ Name: "svc3", }, @@ -565,24 +625,33 @@ func TestServiceToGroupSelector(t *testing.T) { } grp1 := &antreatypes.Group{ - UID: "uid1", - Name: "cgA", + UID: "uid1", + SourceReference: &controlplane.GroupReference{ + Name: "cgA", + UID: "uid1", + }, ServiceReference: &controlplane.ServiceReference{ Name: "svc1", Namespace: metav1.NamespaceDefault, }, } grp2 := &antreatypes.Group{ - UID: "uid2", - Name: "cgB", + UID: "uid2", + SourceReference: &controlplane.GroupReference{ + Name: "cg2", + UID: "uidB", + }, ServiceReference: &controlplane.ServiceReference{ Name: "svc2", Namespace: "test", }, } grp3 := &antreatypes.Group{ - UID: "uid3", - Name: "cgC", + UID: "uid3", + SourceReference: &controlplane.GroupReference{ + Name: "cgC", + UID: "uid3", + }, ServiceReference: &controlplane.ServiceReference{ Name: "svc3", Namespace: "test", @@ -704,33 +773,51 @@ var externalEntities = []*crdv1alpha2.ExternalEntity{ var groups = []antreatypes.Group{ { - UID: "groupUID0", - Name: "group0", + UID: "groupUID0", + SourceReference: &controlplane.GroupReference{ + Name: "group0", + UID: "groupUID0", + }, Selector: antreatypes.NewGroupSelector("test-ns", &metav1.LabelSelector{MatchLabels: map[string]string{"app": "foo"}}, nil, nil, nil), }, { - UID: "groupUID1", - Name: "group1", + UID: "groupUID1", + SourceReference: &controlplane.GroupReference{ + Name: "group1", + UID: "groupUID1", + }, Selector: antreatypes.NewGroupSelector("test-ns", nil, nil, nil, nil), }, { - UID: "groupUID2", - Name: "group2", + UID: "groupUID2", + SourceReference: &controlplane.GroupReference{ + Name: "group2", + UID: "groupUID2", + }, Selector: antreatypes.NewGroupSelector("test-ns", &metav1.LabelSelector{MatchLabels: map[string]string{"app": "other"}}, nil, nil, nil), }, { - UID: "groupUID3", - Name: "group3", + UID: "groupUID3", + SourceReference: &controlplane.GroupReference{ + Name: "group3", + UID: "groupUID3", + }, ChildGroups: []string{"group0", "group1"}, }, { - UID: "groupUID4", - Name: "group4", + UID: "groupUID4", + SourceReference: &controlplane.GroupReference{ + Name: "group4", + UID: "groupUID4", + }, ChildGroups: []string{"group0", "group2"}, }, { - UID: "groupUID5", - Name: "group5", + UID: "groupUID5", + SourceReference: &controlplane.GroupReference{ + Name: "group5", + UID: "groupUID5", + }, ChildGroups: []string{"group1", "group2"}, }, } @@ -777,7 +864,7 @@ func TestGetAssociatedGroups(t *testing.T) { for i, g := range tt.existingGroups { npc.internalGroupStore.Create(&tt.existingGroups[i]) if g.Selector != nil { - npc.groupingInterface.AddGroup(clusterGroupType, g.Name, g.Selector) + npc.groupingInterface.AddGroup(internalGroupType, g.SourceReference.Name, g.Selector) } } groups, err := npc.GetAssociatedGroups(tt.queryName, tt.queryNamespace) @@ -824,8 +911,8 @@ func TestGetGroupMembers(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { npc.internalGroupStore.Create(&tt.group) - npc.groupingInterface.AddGroup(clusterGroupType, tt.group.Name, tt.group.Selector) - members, _, err := npc.GetGroupMembers(tt.group.Name) + npc.groupingInterface.AddGroup(internalGroupType, tt.group.SourceReference.Name, tt.group.Selector) + members, _, err := npc.GetGroupMembers(tt.group.SourceReference.Name) assert.Equal(t, nil, err) assert.Equal(t, tt.expectedMembers, members) }) @@ -946,8 +1033,11 @@ func TestSyncInternalGroup(t *testing.T) { assert.Equal(t, expectedInternalNetworkPolicy2, actualInternalNetworkPolicy2) expectedInternalGroup := &antreatypes.Group{ - UID: cgUID, - Name: cgName, + UID: cgUID, + SourceReference: &controlplane.GroupReference{ + Name: cgName, + UID: cgUID, + }, Selector: antreatypes.NewGroupSelector("", nil, &selectorA, nil, nil), MembersComputed: corev1.ConditionTrue, } @@ -981,3 +1071,29 @@ func TestSyncInternalGroup(t *testing.T) { _, exists, _ = npc.addressGroupStore.Get(cgName) require.False(t, exists, "The AddressGroup for the ClusterGroup should be deleted when it's no longer referenced by any ClusterNetworkPolicy") } + +func TestGetClusterGroupSourceRef(t *testing.T) { + tests := []struct { + name string + group *crdv1alpha3.ClusterGroup + expectedRef *controlplane.GroupReference + }{ + { + name: "cg-ref", + group: &crdv1alpha3.ClusterGroup{ + ObjectMeta: metav1.ObjectMeta{Name: "cgA", UID: "uidA"}, + }, + expectedRef: &controlplane.GroupReference{ + Name: "cgA", + Namespace: "", + UID: "uidA", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actualRef := getClusterGroupSourceRef(tt.group) + assert.Equal(t, tt.expectedRef, actualRef) + }) + } +} diff --git a/pkg/controller/networkpolicy/clusternetworkpolicy.go b/pkg/controller/networkpolicy/clusternetworkpolicy.go index 886c5abdf9e..9d221b0ca3c 100644 --- a/pkg/controller/networkpolicy/clusternetworkpolicy.go +++ b/pkg/controller/networkpolicy/clusternetworkpolicy.go @@ -635,6 +635,7 @@ func (n *NetworkPolicyController) processInternalGroupForRule(group *antreatypes var ipBlocks []controlplane.IPBlock createAddrGroup := false for _, childName := range group.ChildGroups { + childName = k8s.NamespacedName(group.SourceReference.Namespace, childName) childGroup, found, _ := n.internalGroupStore.Get(childName) if found { child := childGroup.(*antreatypes.Group) @@ -648,16 +649,26 @@ func (n *NetworkPolicyController) processInternalGroupForRule(group *antreatypes return createAddrGroup, ipBlocks } -// processRefCG processes the ClusterGroup reference present in the rule and returns the +// processRefGroupOrClusterGroup processes the Group/ClusterGroup reference present in the rule and returns the // NetworkPolicyPeer with the corresponding AddressGroup or IPBlock. -func (n *NetworkPolicyController) processRefCG(g string) (string, []controlplane.IPBlock) { - // Retrieve ClusterGroup for corresponding entry in the rule. - cg, err := n.cgLister.Get(g) - if err != nil { - // The ClusterGroup referred to has not been created yet. - return "", nil +func (n *NetworkPolicyController) processRefGroupOrClusterGroup(g, namespace string) (string, []controlplane.IPBlock) { + var key string + if namespace != "" { + grp, err := n.grpLister.Groups(namespace).Get(g) + if err != nil { + // The Group referred to has not been created yet. + return "", nil + } + key = internalGroupKeyFunc(grp) + } else { + // Retrieve ClusterGroup for corresponding entry in the rule. + cg, err := n.cgLister.Get(g) + if err != nil { + // The ClusterGroup referred to has not been created yet. + return "", nil + } + key = internalGroupKeyFunc(cg) } - key := internalGroupKeyFunc(cg) // Find the internal Group corresponding to this ClusterGroup ig, found, _ := n.internalGroupStore.Get(key) if !found { @@ -667,14 +678,14 @@ func (n *NetworkPolicyController) processRefCG(g string) (string, []controlplane return "", nil } intGrp := ig.(*antreatypes.Group) - // The ClusterGroup referred in the rule might have childGroups defined using selectors + // The Group/ClusterGroup referred in the rule might have childGroups defined using selectors // or ipBlocks (or both). An addressGroup needs to be created as long as there is at least - // one childGroup defined by selectors, or the ClusterGroup itself is defined by selectors. + // one childGroup defined by selectors, or the Group/ClusterGroup itself is defined by selectors. // In case of updates, the original addressGroup created will be de-referenced and cleaned - // up if the ClusterGroup becomes ipBlocks-only. + // up if the Group/ClusterGroup becomes ipBlocks-only. createAddrGroup, ipb := n.processInternalGroupForRule(intGrp) if createAddrGroup { - agKey := n.createAddressGroupForClusterGroupCRD(intGrp) + agKey := n.createAddressGroupForInternalGroup(intGrp) return agKey, ipb } return "", ipb @@ -701,5 +712,5 @@ func (n *NetworkPolicyController) processAppliedToGroupForCG(g string) string { klog.V(2).Infof("ClusterGroup %s with IPBlocks will not be processed as AppliedTo", g) return "" } - return n.createAppliedToGroupForClusterGroupCRD(intGrp) + return n.createAppliedToGroupForInternalGroup(intGrp) } diff --git a/pkg/controller/networkpolicy/clusternetworkpolicy_test.go b/pkg/controller/networkpolicy/clusternetworkpolicy_test.go index b093c0c5e00..e858a385213 100644 --- a/pkg/controller/networkpolicy/clusternetworkpolicy_test.go +++ b/pkg/controller/networkpolicy/clusternetworkpolicy_test.go @@ -2333,7 +2333,7 @@ func TestProcessRefCG(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - actualAG, actualIPB := npc.processRefCG(tt.inputCG) + actualAG, actualIPB := npc.processRefGroupOrClusterGroup(tt.inputCG, "") assert.Equal(t, tt.expectedIPB, actualIPB, "IPBlock does not match") assert.Equal(t, tt.expectedAG, actualAG, "addressGroup does not match") }) diff --git a/pkg/controller/networkpolicy/crd_utils.go b/pkg/controller/networkpolicy/crd_utils.go index 454ef74f832..caec84da569 100644 --- a/pkg/controller/networkpolicy/crd_utils.go +++ b/pkg/controller/networkpolicy/crd_utils.go @@ -16,14 +16,17 @@ package networkpolicy import ( "strings" + "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/conversion" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/klog/v2" "antrea.io/antrea/pkg/apis/controlplane" "antrea.io/antrea/pkg/apis/crd/v1alpha1" + crdv1alpha3 "antrea.io/antrea/pkg/apis/crd/v1alpha3" "antrea.io/antrea/pkg/controller/networkpolicy/store" antreatypes "antrea.io/antrea/pkg/controller/types" "antrea.io/antrea/pkg/util/k8s" @@ -37,6 +40,37 @@ var ( } ) +// semanticIgnoreLastTransitionTime does semantic deep equality checks for +// NetworkPolicyCondition but excludes LastTransitionTime. They are used when +// comparing NetworkPolicyCondition in NetworkPolicyStatus objects to avoid +// unnecessary updates caused different status generation time. +var semanticIgnoreLastTransitionTime = conversion.EqualitiesOrDie( + func(a, b v1alpha1.NetworkPolicyCondition) bool { + a.LastTransitionTime = metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC) + b.LastTransitionTime = metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC) + return a == b + }, +) + +// NetworkPolicyStatusEqual compares two NetworkPolicyStatus objects. It disregards +// the LastTransitionTime field in the status Conditions. +func NetworkPolicyStatusEqual(oldStatus, newStatus v1alpha1.NetworkPolicyStatus) bool { + return semanticIgnoreLastTransitionTime.DeepEqual(oldStatus, newStatus) +} + +// groupMembersComputedConditionEqual checks whether the condition status for GroupMembersComputed condition +// is same. Returns true if equal, otherwise returns false. It disregards the lastTransitionTime field. +func groupMembersComputedConditionEqual(conds []crdv1alpha3.GroupCondition, condition crdv1alpha3.GroupCondition) bool { + for _, c := range conds { + if c.Type == crdv1alpha3.GroupMembersComputed { + if c.Status == condition.Status { + return true + } + } + } + return false +} + // toAntreaServicesForCRD converts a slice of v1alpha1.NetworkPolicyPort objects // and a slice of v1alpha1.NetworkPolicyProtocol objects to a slice of Antrea // Service objects. A bool is returned along with the Service objects to indicate @@ -127,7 +161,7 @@ func (n *NetworkPolicyController) toAntreaPeerForCRD(peers []v1alpha1.NetworkPol } ipBlocks = append(ipBlocks, *ipBlock) } else if peer.Group != "" { - normalizedUID, groupIPBlocks := n.processRefCG(peer.Group) + normalizedUID, groupIPBlocks := n.processRefGroupOrClusterGroup(peer.Group, np.GetNamespace()) if normalizedUID != "" { addressGroups = append(addressGroups, normalizedUID) } @@ -179,11 +213,11 @@ func (n *NetworkPolicyController) svcRefToPeerForCRD(svcRefs []v1alpha1.Namespac return &controlplane.NetworkPolicyPeer{ToServices: controlplaneSvcRefs} } -// createAppliedToGroupForClusterGroupCRD creates an AppliedToGroup object corresponding to a +// createAppliedToGroupForInternalGroup creates an AppliedToGroup object corresponding to an // internal Group. If the AppliedToGroup already exists, it returns the key // otherwise it copies the internal Group contents to an AppliedToGroup resource and returns // its key. -func (n *NetworkPolicyController) createAppliedToGroupForClusterGroupCRD(intGrp *antreatypes.Group) string { +func (n *NetworkPolicyController) createAppliedToGroupForInternalGroup(intGrp *antreatypes.Group) string { key, err := store.GroupKeyFunc(intGrp) if err != nil { return "" @@ -198,7 +232,7 @@ func (n *NetworkPolicyController) createAppliedToGroupForClusterGroupCRD(intGrp UID: intGrp.UID, Name: key, } - klog.V(2).Infof("Creating new AppliedToGroup %v corresponding to ClusterGroup CRD %s", appliedToGroup.UID, intGrp.Name) + klog.V(2).InfoS("Creating new AppliedToGroup corresponding to internal Group", "AppliedToGroup", appliedToGroup.UID, "internalGroup", intGrp.SourceReference.ToTypedString()) n.appliedToGroupStore.Create(appliedToGroup) n.enqueueAppliedToGroup(key) return key @@ -231,7 +265,7 @@ func (n *NetworkPolicyController) createAppliedToGroupForService(service *v1alph // ClusterGroup spec. If the AddressGroup already exists, it returns the key // otherwise it copies the ClusterGroup CRD contents to an AddressGroup resource and returns // its key. If the corresponding internal Group is not found return empty. -func (n *NetworkPolicyController) createAddressGroupForClusterGroupCRD(intGrp *antreatypes.Group) string { +func (n *NetworkPolicyController) createAddressGroupForInternalGroup(intGrp *antreatypes.Group) string { key, err := store.GroupKeyFunc(intGrp) if err != nil { return "" @@ -247,7 +281,7 @@ func (n *NetworkPolicyController) createAddressGroupForClusterGroupCRD(intGrp *a Name: key, } n.addressGroupStore.Create(addressGroup) - klog.V(2).Infof("Created new AddressGroup %v corresponding to ClusterGroup CRD %s", addressGroup.UID, intGrp.Name) + klog.V(2).InfoS("Created new AddressGroup corresponding to internal Group", "AddressGroup", addressGroup.UID, "internalGroup", intGrp.SourceReference.ToTypedString()) return key } @@ -284,3 +318,22 @@ func getNormalizedNameForSelector(sel *antreatypes.GroupSelector) string { } return "" } + +func (n *NetworkPolicyController) syncInternalGroup(key string) error { + defer n.triggerANPUpdates(key) + defer n.triggerCNPUpdates(key) + defer n.triggerParentGroupSync(key) + // Retrieve the internal Group corresponding to this key. + grpObj, found, _ := n.internalGroupStore.Get(key) + if !found { + klog.V(2).InfoS("Internal group not found.", "internalGroup", key) + n.groupingInterface.DeleteGroup(internalGroupType, key) + return nil + } + grp := grpObj.(*antreatypes.Group) + if grp.SourceReference.Namespace != "" { + // Sync the Group as a Namespaced Group. + return n.syncInternalNamespacedGroup(grp) + } + return n.syncInternalClusterGroup(grp) +} diff --git a/pkg/controller/networkpolicy/crd_utils_test.go b/pkg/controller/networkpolicy/crd_utils_test.go index 90a1cfbdc49..4b46e9ed793 100644 --- a/pkg/controller/networkpolicy/crd_utils_test.go +++ b/pkg/controller/networkpolicy/crd_utils_test.go @@ -443,3 +443,150 @@ func TestToAntreaPeerForCRD(t *testing.T) { }) } } + +func TestProcessRefGroupOrClusterGroup(t *testing.T) { + selectorA := metav1.LabelSelector{MatchLabels: map[string]string{"foo1": "bar1"}} + cidr := "10.0.0.0/24" + cidrIPNet, _ := cidrStrToIPNet(cidr) + // cgA with selector present in cache + cgA := crdv1alpha3.ClusterGroup{ + ObjectMeta: metav1.ObjectMeta{Name: "cgA", UID: "uidA"}, + Spec: crdv1alpha3.GroupSpec{ + NamespaceSelector: &selectorA, + }, + } + // cgB with IPBlock present in cache + cgB := crdv1alpha3.ClusterGroup{ + ObjectMeta: metav1.ObjectMeta{Name: "cgB", UID: "uidB"}, + Spec: crdv1alpha3.GroupSpec{ + IPBlocks: []crdv1alpha1.IPBlock{ + { + CIDR: cidr, + }, + }, + }, + } + // cgC not found in cache + cgC := crdv1alpha3.ClusterGroup{ + ObjectMeta: metav1.ObjectMeta{Name: "cgC", UID: "uidC"}, + Spec: crdv1alpha3.GroupSpec{ + NamespaceSelector: &selectorA, + }, + } + + // gA with selector present in cache + gA := crdv1alpha3.Group{ + ObjectMeta: metav1.ObjectMeta{Namespace: "nsA", Name: "gA", UID: "uidGA"}, + Spec: crdv1alpha3.GroupSpec{ + NamespaceSelector: &selectorA, + }, + } + // gB with IPBlock present in cache + gB := crdv1alpha3.Group{ + ObjectMeta: metav1.ObjectMeta{Namespace: "nsB", Name: "gB", UID: "uidGB"}, + Spec: crdv1alpha3.GroupSpec{ + IPBlocks: []crdv1alpha1.IPBlock{ + { + CIDR: cidr, + }, + }, + }, + } + // gC not found in cache + gC := crdv1alpha3.Group{ + ObjectMeta: metav1.ObjectMeta{Namespace: "nsC", Name: "gC", UID: "uidGC"}, + Spec: crdv1alpha3.GroupSpec{ + NamespaceSelector: &selectorA, + }, + } + _, npc := newController() + npc.addClusterGroup(&cgA) + npc.addClusterGroup(&cgB) + npc.cgStore.Add(&cgA) + npc.cgStore.Add(&cgB) + npc.addGroup(&gA) + npc.addGroup(&gB) + npc.gStore.Add(&gA) + npc.gStore.Add(&gB) + tests := []struct { + name string + inputGroupOrCG string + inputNamespace string + expectedAG string + expectedIPB []controlplane.IPBlock + }{ + { + name: "empty-cg-no-result", + inputGroupOrCG: "", + inputNamespace: "", + expectedAG: "", + expectedIPB: nil, + }, + { + name: "cg-with-selector", + inputGroupOrCG: cgA.Name, + inputNamespace: "", + expectedAG: cgA.Name, + expectedIPB: nil, + }, + { + name: "cg-with-selector-not-found", + inputGroupOrCG: cgC.Name, + inputNamespace: "", + expectedAG: "", + expectedIPB: nil, + }, + { + name: "cg-with-ipblock", + inputGroupOrCG: cgB.Name, + inputNamespace: "", + expectedAG: "", + expectedIPB: []controlplane.IPBlock{ + { + CIDR: *cidrIPNet, + Except: []controlplane.IPNet{}, + }, + }, + }, + { + name: "empty-g-no-result", + inputGroupOrCG: "", + inputNamespace: "", + expectedAG: "", + expectedIPB: nil, + }, + { + name: "g-with-selector", + inputGroupOrCG: gA.Name, + inputNamespace: gA.Namespace, + expectedAG: fmt.Sprintf("%s/%s", gA.Namespace, gA.Name), + expectedIPB: nil, + }, + { + name: "g-with-selector-not-found", + inputGroupOrCG: gC.Name, + inputNamespace: gC.Namespace, + expectedAG: "", + expectedIPB: nil, + }, + { + name: "g-with-ipblock", + inputGroupOrCG: gB.Name, + inputNamespace: gB.Namespace, + expectedAG: "", + expectedIPB: []controlplane.IPBlock{ + { + CIDR: *cidrIPNet, + Except: []controlplane.IPNet{}, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actualAG, actualIPB := npc.processRefGroupOrClusterGroup(tt.inputGroupOrCG, tt.inputNamespace) + assert.Equal(t, tt.expectedIPB, actualIPB, "IPBlock does not match") + assert.Equal(t, tt.expectedAG, actualAG, "addressGroup does not match") + }) + } +} diff --git a/pkg/controller/networkpolicy/group.go b/pkg/controller/networkpolicy/group.go new file mode 100644 index 00000000000..6f328ca92f4 --- /dev/null +++ b/pkg/controller/networkpolicy/group.go @@ -0,0 +1,282 @@ +// Copyright 2021 Antrea Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package networkpolicy + +import ( + "context" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/client-go/tools/cache" + "k8s.io/klog/v2" + + "antrea.io/antrea/pkg/apis/controlplane" + crdv1alpha1 "antrea.io/antrea/pkg/apis/crd/v1alpha1" + crdv1alpha3 "antrea.io/antrea/pkg/apis/crd/v1alpha3" + antreatypes "antrea.io/antrea/pkg/controller/types" +) + +// addGroup is responsible for processing the ADD event of a Group resource. +func (n *NetworkPolicyController) addGroup(curObj interface{}) { + g := curObj.(*crdv1alpha3.Group) + key := internalGroupKeyFunc(g) + klog.V(2).InfoS("Processing ADD event for Group", "Group", key) + newGroup := n.processGroup(g) + klog.V(2).InfoS("Creating new internal Group", "internalGroup", newGroup.UID) + n.internalGroupStore.Create(newGroup) + n.enqueueInternalGroup(key) +} + +// updateGroup is responsible for processing the UPDATE event of a Group resource. +func (n *NetworkPolicyController) updateGroup(oldObj, curObj interface{}) { + cg := curObj.(*crdv1alpha3.Group) + og := oldObj.(*crdv1alpha3.Group) + key := internalGroupKeyFunc(cg) + klog.V(2).InfoS("Processing UPDATE event for Group", "Group", key) + newGroup := n.processGroup(cg) + oldGroup := n.processGroup(og) + + selectorUpdated := func() bool { + return getNormalizedNameForSelector(newGroup.Selector) != getNormalizedNameForSelector(oldGroup.Selector) + } + svcRefUpdated := func() bool { + oldSvc, newSvc := oldGroup.ServiceReference, newGroup.ServiceReference + if oldSvc != nil && newSvc != nil && oldSvc.Name == newSvc.Name && oldSvc.Namespace == newSvc.Namespace { + return false + } else if oldSvc == nil && newSvc == nil { + return false + } + return true + } + ipBlocksUpdated := func() bool { + oldIPBs, newIPBs := sets.String{}, sets.String{} + for _, ipb := range oldGroup.IPBlocks { + oldIPBs.Insert(ipb.CIDR.String()) + } + for _, ipb := range newGroup.IPBlocks { + newIPBs.Insert(ipb.CIDR.String()) + } + return oldIPBs.Equal(newIPBs) + } + childGroupsUpdated := func() bool { + oldChildGroups, newChildGroups := sets.String{}, sets.String{} + for _, c := range oldGroup.ChildGroups { + oldChildGroups.Insert(c) + } + for _, c := range newGroup.ChildGroups { + newChildGroups.Insert(c) + } + return !oldChildGroups.Equal(newChildGroups) + } + if !ipBlocksUpdated() && !svcRefUpdated() && !selectorUpdated() && !childGroupsUpdated() { + // No change in the contents of the Group. No need to enqueue for further sync. + return + } + n.internalGroupStore.Update(newGroup) + n.enqueueInternalGroup(key) +} + +// deleteGroup is responsible for processing the DELETE event of a Group resource. +func (n *NetworkPolicyController) deleteGroup(oldObj interface{}) { + og, ok := oldObj.(*crdv1alpha3.Group) + klog.V(2).InfoS("Processing DELETE event for Group", "Group", internalGroupKeyFunc(og)) + if !ok { + tombstone, ok := oldObj.(cache.DeletedFinalStateUnknown) + if !ok { + klog.Errorf("Error decoding object when deleting Group, invalid type: %v", oldObj) + return + } + og, ok = tombstone.Obj.(*crdv1alpha3.Group) + if !ok { + klog.Errorf("Error decoding object tombstone when deleting Group, invalid type: %v", tombstone.Obj) + return + } + } + key := internalGroupKeyFunc(og) + klog.V(2).InfoS("Deleting internal Group", "Group", key) + err := n.internalGroupStore.Delete(key) + if err != nil { + klog.Errorf("Unable to delete internal Group %s from store: %v", key, err) + } + n.enqueueInternalGroup(key) +} + +func (n *NetworkPolicyController) processGroup(g *crdv1alpha3.Group) *antreatypes.Group { + internalGroup := antreatypes.Group{ + SourceReference: getGroupSourceRef(g), + UID: g.UID, + } + if len(g.Spec.ChildGroups) > 0 { + for _, childGName := range g.Spec.ChildGroups { + internalGroup.ChildGroups = append(internalGroup.ChildGroups, string(childGName)) + } + return &internalGroup + } + if len(g.Spec.IPBlocks) > 0 { + for i := range g.Spec.IPBlocks { + ipb, _ := toAntreaIPBlockForCRD(&g.Spec.IPBlocks[i]) + internalGroup.IPBlocks = append(internalGroup.IPBlocks, *ipb) + } + return &internalGroup + } + svcSelector := g.Spec.ServiceReference + if svcSelector != nil { + // ServiceReference will be converted to groupSelector once the internalGroup is synced. + internalGroup.ServiceReference = &controlplane.ServiceReference{ + Namespace: svcSelector.Namespace, + Name: svcSelector.Name, + } + } else { + groupSelector := antreatypes.NewGroupSelector(g.Namespace, g.Spec.PodSelector, g.Spec.NamespaceSelector, g.Spec.ExternalEntitySelector, nil) + internalGroup.Selector = groupSelector + } + return &internalGroup +} + +func getGroupSourceRef(g *crdv1alpha3.Group) *controlplane.GroupReference { + return &controlplane.GroupReference{ + Name: g.GetName(), + Namespace: g.GetNamespace(), + UID: g.GetUID(), + } +} + +func (n *NetworkPolicyController) syncInternalNamespacedGroup(grp *antreatypes.Group) error { + originalMembersComputedStatus := grp.MembersComputed + // Retrieve the Group corresponding to this key. + g, err := n.grpLister.Groups(grp.SourceReference.Namespace).Get(grp.SourceReference.Name) + if err != nil { + klog.InfoS("Didn't find Group, skip processing of internal group", "Group", grp.SourceReference.ToTypedString()) + return nil + } + key := internalGroupKeyFunc(g) + selectorUpdated := n.processServiceReference(grp) + if grp.Selector != nil { + n.groupingInterface.AddGroup(internalGroupType, key, grp.Selector) + } else { + n.groupingInterface.DeleteGroup(internalGroupType, key) + } + + membersComputed, membersComputedStatus := true, v1.ConditionFalse + // Update the Group status to Realized as Antrea has recognized the Group and + // processed its group members. The Group is considered realized if: + // 1. It does not have child groups. The group members are immediately considered + // computed during syncInternalGroup, as the group selector is finalized. + // 2. All its child groups are created and realized. + if len(grp.ChildGroups) > 0 { + for _, cgName := range grp.ChildGroups { + internalGroup, found, _ := n.internalGroupStore.Get(grp.SourceReference.Namespace + "/" + cgName) + if !found || internalGroup.(*antreatypes.Group).MembersComputed != v1.ConditionTrue { + membersComputed = false + break + } + } + } + if membersComputed { + klog.V(4).InfoS("Updating GroupMembersComputed Status for Group %s/%s", "Group", key) + err = n.updateGroupStatus(g, v1.ConditionTrue) + if err != nil { + klog.Errorf("Failed to update Group %s/%s GroupMembersComputed condition to %s: %v", g.Namespace, g.Name, v1.ConditionTrue, err) + } else { + membersComputedStatus = v1.ConditionTrue + } + } + if selectorUpdated || membersComputedStatus != originalMembersComputedStatus { + // Update the internal Group object in the store with the new selector and status. + updatedGrp := &antreatypes.Group{ + UID: grp.UID, + SourceReference: grp.SourceReference, + MembersComputed: membersComputedStatus, + Selector: grp.Selector, + IPBlocks: grp.IPBlocks, + ServiceReference: grp.ServiceReference, + ChildGroups: grp.ChildGroups, + } + klog.V(2).InfoS("Updating existing internal Group", "internalGroup", grp.SourceReference.ToGroupName()) + n.internalGroupStore.Update(updatedGrp) + } + return err +} + +// triggerANPUpdates triggers processing of Antrea NetworkPolicies associated with the input Group. +func (n *NetworkPolicyController) triggerANPUpdates(g string) { + // If a Group is added/updated, it might have a reference in Antrea NetworkPolicy. + anps, err := n.anpInformer.Informer().GetIndexer().ByIndex(GroupIndex, g) + if err != nil { + klog.Errorf("Error retrieving Antrea NetworkPolicies corresponding to Group %s", g) + return + } + for _, obj := range anps { + anp := obj.(*crdv1alpha1.NetworkPolicy) + // Re-process Antrea NetworkPolicies which may be affected due to updates to Group. + curInternalNP := n.processAntreaNetworkPolicy(anp) + klog.V(2).InfoS("Updating existing internal NetworkPolicy for Antrea NetworkPolicy", "internalNP", curInternalNP.Name, "ANP", curInternalNP.SourceRef.ToString()) + key := internalNetworkPolicyKeyFunc(anp) + // Lock access to internal NetworkPolicy store such that concurrent access + // to an internal NetworkPolicy is not allowed. This will avoid the + // case in which an Update to an internal NetworkPolicy object may + // cause the SpanMeta member to be overridden with stale SpanMeta members + // from an older internal NetworkPolicy. + n.internalNetworkPolicyMutex.Lock() + oldInternalNPObj, _, _ := n.internalNetworkPolicyStore.Get(key) + oldInternalNP := oldInternalNPObj.(*antreatypes.NetworkPolicy) + // Must preserve old internal NetworkPolicy Span. + curInternalNP.SpanMeta = oldInternalNP.SpanMeta + n.internalNetworkPolicyStore.Update(curInternalNP) + // Unlock the internal NetworkPolicy store. + n.internalNetworkPolicyMutex.Unlock() + // Enqueue addressGroup keys to update their group members. + // TODO: optimize this to avoid enqueueing address groups when not updated. + for _, atg := range curInternalNP.AppliedToGroups { + n.enqueueAppliedToGroup(atg) + } + for _, rule := range curInternalNP.Rules { + for _, addrGroupName := range rule.From.AddressGroups { + n.enqueueAddressGroup(addrGroupName) + } + for _, addrGroupName := range rule.To.AddressGroups { + n.enqueueAddressGroup(addrGroupName) + } + } + n.enqueueInternalNetworkPolicy(key) + for _, atg := range oldInternalNP.AppliedToGroups { + n.deleteDereferencedAppliedToGroup(atg) + } + n.deleteDereferencedAddressGroups(oldInternalNP) + } + return +} + +// updateGroupStatus updates the Status subresource for a Group. +func (n *NetworkPolicyController) updateGroupStatus(g *crdv1alpha3.Group, cStatus v1.ConditionStatus) error { + condStatus := crdv1alpha3.GroupCondition{ + Status: cStatus, + Type: crdv1alpha3.GroupMembersComputed, + } + if groupMembersComputedConditionEqual(g.Status.Conditions, condStatus) { + // There is no change in conditions. + return nil + } + condStatus.LastTransitionTime = metav1.Now() + status := crdv1alpha3.GroupStatus{ + Conditions: []crdv1alpha3.GroupCondition{condStatus}, + } + klog.V(4).InfoS("Updating Group status", "Group", internalGroupKeyFunc(g), "status", condStatus) + toUpdate := g.DeepCopy() + toUpdate.Status = status + _, err := n.crdClient.CrdV1alpha3().Groups(g.GetNamespace()).UpdateStatus(context.TODO(), toUpdate, metav1.UpdateOptions{}) + return err +} diff --git a/pkg/controller/networkpolicy/group_test.go b/pkg/controller/networkpolicy/group_test.go new file mode 100644 index 00000000000..b77245e45fe --- /dev/null +++ b/pkg/controller/networkpolicy/group_test.go @@ -0,0 +1,532 @@ +// Copyright 2021 Antrea Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package networkpolicy + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "antrea.io/antrea/pkg/apis/controlplane" + crdv1alpha1 "antrea.io/antrea/pkg/apis/crd/v1alpha1" + crdv1alpha3 "antrea.io/antrea/pkg/apis/crd/v1alpha3" + antreatypes "antrea.io/antrea/pkg/controller/types" +) + +func TestProcessGroup(t *testing.T) { + selectorA := metav1.LabelSelector{MatchLabels: map[string]string{"foo1": "bar1"}} + selectorB := metav1.LabelSelector{MatchLabels: map[string]string{"foo2": "bar2"}} + selectorC := metav1.LabelSelector{MatchLabels: map[string]string{"foo3": "bar3"}} + selectorD := metav1.LabelSelector{MatchLabels: map[string]string{"foo4": "bar4"}} + cidr := "10.0.0.0/24" + cidrIPNet, _ := cidrStrToIPNet(cidr) + tests := []struct { + name string + inputGroup *crdv1alpha3.Group + expectedGroup *antreatypes.Group + }{ + { + name: "g-with-ns-selector", + inputGroup: &crdv1alpha3.Group{ + ObjectMeta: metav1.ObjectMeta{Namespace: "nsA", Name: "gA", UID: "uidA"}, + Spec: crdv1alpha3.GroupSpec{ + NamespaceSelector: &selectorA, + }, + }, + expectedGroup: &antreatypes.Group{ + UID: "uidA", + SourceReference: &controlplane.GroupReference{ + Name: "gA", + Namespace: "nsA", + UID: "uidA", + }, + Selector: antreatypes.NewGroupSelector("nsA", nil, &selectorA, nil, nil), + }, + }, + { + name: "g-with-pod-selector", + inputGroup: &crdv1alpha3.Group{ + ObjectMeta: metav1.ObjectMeta{Namespace: "nsB", Name: "gB", UID: "uidB"}, + Spec: crdv1alpha3.GroupSpec{ + PodSelector: &selectorB, + }, + }, + expectedGroup: &antreatypes.Group{ + UID: "uidB", + SourceReference: &controlplane.GroupReference{ + Name: "gB", + Namespace: "nsB", + UID: "uidB", + }, + Selector: antreatypes.NewGroupSelector("nsB", &selectorB, nil, nil, nil), + }, + }, + { + name: "g-with-pod-ns-selector", + inputGroup: &crdv1alpha3.Group{ + ObjectMeta: metav1.ObjectMeta{Namespace: "nsC", Name: "gC", UID: "uidC"}, + Spec: crdv1alpha3.GroupSpec{ + NamespaceSelector: &selectorD, + PodSelector: &selectorC, + }, + }, + expectedGroup: &antreatypes.Group{ + UID: "uidC", + SourceReference: &controlplane.GroupReference{ + Name: "gC", + Namespace: "nsC", + UID: "uidC", + }, + Selector: antreatypes.NewGroupSelector("nsC", &selectorC, &selectorD, nil, nil), + }, + }, + { + name: "g-with-ip-block", + inputGroup: &crdv1alpha3.Group{ + ObjectMeta: metav1.ObjectMeta{Namespace: "nsD", Name: "gD", UID: "uidD"}, + Spec: crdv1alpha3.GroupSpec{ + IPBlocks: []crdv1alpha1.IPBlock{ + { + CIDR: cidr, + }, + }, + }, + }, + expectedGroup: &antreatypes.Group{ + UID: "uidD", + SourceReference: &controlplane.GroupReference{ + Name: "gD", + Namespace: "nsD", + UID: "uidD", + }, + IPBlocks: []controlplane.IPBlock{ + { + CIDR: *cidrIPNet, + Except: []controlplane.IPNet{}, + }, + }, + }, + }, + { + name: "g-with-svc-reference", + inputGroup: &crdv1alpha3.Group{ + ObjectMeta: metav1.ObjectMeta{Namespace: "nsE", Name: "gE", UID: "uidE"}, + Spec: crdv1alpha3.GroupSpec{ + ServiceReference: &crdv1alpha1.NamespacedName{ + Name: "test-svc", + Namespace: "nsE", + }, + }, + }, + expectedGroup: &antreatypes.Group{ + UID: "uidE", + SourceReference: &controlplane.GroupReference{ + Name: "gE", + Namespace: "nsE", + UID: "uidE", + }, + ServiceReference: &controlplane.ServiceReference{ + Name: "test-svc", + Namespace: "nsE", + }, + }, + }, + { + name: "g-with-child-groups", + inputGroup: &crdv1alpha3.Group{ + ObjectMeta: metav1.ObjectMeta{Namespace: "nsF", Name: "gF", UID: "uidF"}, + Spec: crdv1alpha3.GroupSpec{ + ChildGroups: []crdv1alpha3.ClusterGroupReference{"gA", "gB"}, + }, + }, + expectedGroup: &antreatypes.Group{ + UID: "uidF", + SourceReference: &controlplane.GroupReference{ + Name: "gF", + Namespace: "nsF", + UID: "uidF", + }, + ChildGroups: []string{"gA", "gB"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, c := newController() + actualGroup := c.processGroup(tt.inputGroup) + assert.Equal(t, tt.expectedGroup, actualGroup) + }) + } +} + +func TestAddGroup(t *testing.T) { + selectorA := metav1.LabelSelector{MatchLabels: map[string]string{"foo1": "bar1"}} + selectorB := metav1.LabelSelector{MatchLabels: map[string]string{"foo2": "bar2"}} + selectorC := metav1.LabelSelector{MatchLabels: map[string]string{"foo3": "bar3"}} + selectorD := metav1.LabelSelector{MatchLabels: map[string]string{"foo4": "bar4"}} + cidr := "10.0.0.0/24" + cidrIPNet, _ := cidrStrToIPNet(cidr) + tests := []struct { + name string + inputGroup *crdv1alpha3.Group + expectedGroup *antreatypes.Group + }{ + { + name: "g-with-ns-selector", + inputGroup: &crdv1alpha3.Group{ + ObjectMeta: metav1.ObjectMeta{Namespace: "nsA", Name: "gA", UID: "uidA"}, + Spec: crdv1alpha3.GroupSpec{ + NamespaceSelector: &selectorA, + }, + }, + expectedGroup: &antreatypes.Group{ + UID: "uidA", + SourceReference: &controlplane.GroupReference{ + Name: "gA", + Namespace: "nsA", + UID: "uidA", + }, + Selector: antreatypes.NewGroupSelector("nsA", nil, &selectorA, nil, nil), + }, + }, + { + name: "g-with-pod-selector", + inputGroup: &crdv1alpha3.Group{ + ObjectMeta: metav1.ObjectMeta{Namespace: "nsB", Name: "gB", UID: "uidB"}, + Spec: crdv1alpha3.GroupSpec{ + PodSelector: &selectorB, + }, + }, + expectedGroup: &antreatypes.Group{ + UID: "uidB", + SourceReference: &controlplane.GroupReference{ + Name: "gB", + Namespace: "nsB", + UID: "uidB", + }, + Selector: antreatypes.NewGroupSelector("nsB", &selectorB, nil, nil, nil), + }, + }, + { + name: "g-with-pod-ns-selector", + inputGroup: &crdv1alpha3.Group{ + ObjectMeta: metav1.ObjectMeta{Namespace: "nsC", Name: "gC", UID: "uidC"}, + Spec: crdv1alpha3.GroupSpec{ + NamespaceSelector: &selectorD, + PodSelector: &selectorC, + }, + }, + expectedGroup: &antreatypes.Group{ + UID: "uidC", + SourceReference: &controlplane.GroupReference{ + Name: "gC", + Namespace: "nsC", + UID: "uidC", + }, + Selector: antreatypes.NewGroupSelector("nsC", &selectorC, &selectorD, nil, nil), + }, + }, + { + name: "g-with-ip-block", + inputGroup: &crdv1alpha3.Group{ + ObjectMeta: metav1.ObjectMeta{Namespace: "nsD", Name: "gD", UID: "uidD"}, + Spec: crdv1alpha3.GroupSpec{ + IPBlocks: []crdv1alpha1.IPBlock{ + { + CIDR: cidr, + }, + }, + }, + }, + expectedGroup: &antreatypes.Group{ + UID: "uidD", + SourceReference: &controlplane.GroupReference{ + Name: "gD", + Namespace: "nsD", + UID: "uidD", + }, + IPBlocks: []controlplane.IPBlock{ + { + CIDR: *cidrIPNet, + Except: []controlplane.IPNet{}, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, npc := newController() + npc.addGroup(tt.inputGroup) + key := fmt.Sprintf("%s/%s", tt.inputGroup.Namespace, tt.inputGroup.Name) + actualGroupObj, _, _ := npc.internalGroupStore.Get(key) + actualGroup := actualGroupObj.(*antreatypes.Group) + assert.Equal(t, tt.expectedGroup, actualGroup) + }) + } +} + +func TestUpdateGroup(t *testing.T) { + selectorA := metav1.LabelSelector{MatchLabels: map[string]string{"foo1": "bar1"}} + selectorB := metav1.LabelSelector{MatchLabels: map[string]string{"foo2": "bar2"}} + selectorC := metav1.LabelSelector{MatchLabels: map[string]string{"foo3": "bar3"}} + selectorD := metav1.LabelSelector{MatchLabels: map[string]string{"foo4": "bar4"}} + testG := crdv1alpha3.Group{ + ObjectMeta: metav1.ObjectMeta{Namespace: "nsA", Name: "gA", UID: "uidA"}, + Spec: crdv1alpha3.GroupSpec{ + NamespaceSelector: &selectorA, + }, + } + cidr := "10.0.0.0/24" + cidrIPNet, _ := cidrStrToIPNet(cidr) + tests := []struct { + name string + updatedGroup *crdv1alpha3.Group + expectedGroup *antreatypes.Group + }{ + { + name: "g-update-ns-selector", + updatedGroup: &crdv1alpha3.Group{ + ObjectMeta: metav1.ObjectMeta{Namespace: "nsA", Name: "gA", UID: "uidA"}, + Spec: crdv1alpha3.GroupSpec{ + NamespaceSelector: &selectorB, + }, + }, + expectedGroup: &antreatypes.Group{ + UID: "uidA", + SourceReference: &controlplane.GroupReference{ + Name: "gA", + Namespace: "nsA", + UID: "uidA", + }, + Selector: antreatypes.NewGroupSelector("nsA", nil, &selectorB, nil, nil), + }, + }, + { + name: "g-update-pod-selector", + updatedGroup: &crdv1alpha3.Group{ + ObjectMeta: metav1.ObjectMeta{Namespace: "nsA", Name: "gA", UID: "uidA"}, + Spec: crdv1alpha3.GroupSpec{ + PodSelector: &selectorC, + }, + }, + expectedGroup: &antreatypes.Group{ + UID: "uidA", + SourceReference: &controlplane.GroupReference{ + Name: "gA", + Namespace: "nsA", + UID: "uidA", + }, + Selector: antreatypes.NewGroupSelector("nsA", &selectorC, nil, nil, nil), + }, + }, + { + name: "g-update-pod-ns-selector", + updatedGroup: &crdv1alpha3.Group{ + ObjectMeta: metav1.ObjectMeta{Namespace: "nsA", Name: "gA", UID: "uidA"}, + Spec: crdv1alpha3.GroupSpec{ + NamespaceSelector: &selectorD, + PodSelector: &selectorC, + }, + }, + expectedGroup: &antreatypes.Group{ + UID: "uidA", + SourceReference: &controlplane.GroupReference{ + Name: "gA", + Namespace: "nsA", + UID: "uidA", + }, + Selector: antreatypes.NewGroupSelector("nsA", &selectorC, &selectorD, nil, nil), + }, + }, + { + name: "g-update-ip-block", + updatedGroup: &crdv1alpha3.Group{ + ObjectMeta: metav1.ObjectMeta{Namespace: "nsA", Name: "gA", UID: "uidA"}, + Spec: crdv1alpha3.GroupSpec{ + IPBlocks: []crdv1alpha1.IPBlock{ + { + CIDR: cidr, + }, + }, + }, + }, + expectedGroup: &antreatypes.Group{ + UID: "uidA", + SourceReference: &controlplane.GroupReference{ + Name: "gA", + Namespace: "nsA", + UID: "uidA", + }, + IPBlocks: []controlplane.IPBlock{ + { + CIDR: *cidrIPNet, + Except: []controlplane.IPNet{}, + }, + }, + }, + }, + { + name: "g-update-svc-reference", + updatedGroup: &crdv1alpha3.Group{ + ObjectMeta: metav1.ObjectMeta{Namespace: "nsA", Name: "gA", UID: "uidA"}, + Spec: crdv1alpha3.GroupSpec{ + ServiceReference: &crdv1alpha1.NamespacedName{ + Name: "test-svc", + Namespace: "nsA", + }, + }, + }, + expectedGroup: &antreatypes.Group{ + UID: "uidA", + SourceReference: &controlplane.GroupReference{ + Name: "gA", + Namespace: "nsA", + UID: "uidA", + }, + ServiceReference: &controlplane.ServiceReference{ + Name: "test-svc", + Namespace: "nsA", + }, + }, + }, + { + name: "g-update-child-groups", + updatedGroup: &crdv1alpha3.Group{ + ObjectMeta: metav1.ObjectMeta{Namespace: "nsA", Name: "gA", UID: "uidA"}, + Spec: crdv1alpha3.GroupSpec{ + ChildGroups: []crdv1alpha3.ClusterGroupReference{"gB", "gC"}, + }, + }, + expectedGroup: &antreatypes.Group{ + UID: "uidA", + SourceReference: &controlplane.GroupReference{ + Name: "gA", + Namespace: "nsA", + UID: "uidA", + }, + ChildGroups: []string{"gB", "gC"}, + }, + }, + } + _, npc := newController() + npc.addGroup(&testG) + key := fmt.Sprintf("%s/%s", testG.Namespace, testG.Name) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + npc.updateGroup(&testG, tt.updatedGroup) + actualGroupObj, _, _ := npc.internalGroupStore.Get(key) + actualGroup := actualGroupObj.(*antreatypes.Group) + assert.Equal(t, tt.expectedGroup, actualGroup) + }) + } +} + +func TestDeleteG(t *testing.T) { + selectorA := metav1.LabelSelector{MatchLabels: map[string]string{"foo1": "bar1"}} + testG := crdv1alpha3.Group{ + ObjectMeta: metav1.ObjectMeta{Namespace: "nsA", Name: "gA", UID: "uidA"}, + Spec: crdv1alpha3.GroupSpec{ + NamespaceSelector: &selectorA, + }, + } + key := fmt.Sprintf("%s/%s", testG.Namespace, testG.Name) + _, npc := newController() + npc.addGroup(&testG) + npc.deleteGroup(&testG) + _, found, _ := npc.internalGroupStore.Get(key) + assert.False(t, found, "expected internal Group to be deleted") +} + +func TestGroupMembersComputedConditionEqual(t *testing.T) { + tests := []struct { + name string + existingConds []crdv1alpha3.GroupCondition + checkStatus corev1.ConditionStatus + expValue bool + }{ + { + name: "groupmem-cond-exists-not-equal", + existingConds: []crdv1alpha3.GroupCondition{ + { + Type: crdv1alpha3.GroupMembersComputed, + Status: corev1.ConditionFalse, + }, + }, + checkStatus: corev1.ConditionTrue, + expValue: false, + }, + { + name: "groupmem-cond-exists-equal", + existingConds: []crdv1alpha3.GroupCondition{ + { + Type: crdv1alpha3.GroupMembersComputed, + Status: corev1.ConditionTrue, + }, + }, + checkStatus: corev1.ConditionTrue, + expValue: true, + }, + { + name: "groupmem-cond-not-exists-not-equal", + existingConds: []crdv1alpha3.GroupCondition{ + { + Status: corev1.ConditionFalse, + }, + }, + checkStatus: corev1.ConditionTrue, + expValue: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + inCond := crdv1alpha3.GroupCondition{ + Type: crdv1alpha3.GroupMembersComputed, + Status: tt.checkStatus, + } + actualValue := groupMembersComputedConditionEqual(tt.existingConds, inCond) + assert.Equal(t, tt.expValue, actualValue) + }) + } +} + +func TestGetGroupSourceRef(t *testing.T) { + tests := []struct { + name string + group *crdv1alpha3.Group + expectedRef *controlplane.GroupReference + }{ + { + name: "cg-ref", + group: &crdv1alpha3.Group{ + ObjectMeta: metav1.ObjectMeta{Namespace: "nsA", Name: "gA", UID: "uidA"}, + }, + expectedRef: &controlplane.GroupReference{ + Name: "gA", + Namespace: "nsA", + UID: "uidA", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actualRef := getGroupSourceRef(tt.group) + assert.Equal(t, tt.expectedRef, actualRef) + }) + } +} diff --git a/pkg/controller/networkpolicy/networkpolicy_controller.go b/pkg/controller/networkpolicy/networkpolicy_controller.go index ce5e819eaaf..ae0f3aaded6 100644 --- a/pkg/controller/networkpolicy/networkpolicy_controller.go +++ b/pkg/controller/networkpolicy/networkpolicy_controller.go @@ -81,12 +81,15 @@ const ( PriorityIndex = "priority" // ClusterGroupIndex is used to index ClusterNetworkPolicies by ClusterGroup names. ClusterGroupIndex = "clustergroup" + // GroupIndex is used to index Antrea NetworkPolicies by Group names. + GroupIndex = "group" + // EnableNPLoggingAnnotationKey can be added to Namespace to enable logging K8s NP. EnableNPLoggingAnnotationKey = "networkpolicy.antrea.io/enable-logging" appliedToGroupType grouping.GroupType = "appliedToGroup" addressGroupType grouping.GroupType = "addressGroup" - clusterGroupType grouping.GroupType = "clusterGroup" + internalGroupType grouping.GroupType = "internalGroup" ) var ( @@ -183,6 +186,14 @@ type NetworkPolicyController struct { // nodeListerSynced is a function which returns true if the Node shared informer has been synced at least once. nodeListerSynced cache.InformerSynced + grpInformer crdv1a3informers.GroupInformer + // grpLister is able to list/get Groups and is populated by the shared informer passed to + // NewGroupController. + grpLister crdv1a3listers.GroupLister + // grpListerSynced is a function which returns true if the Group shared informer has been synced at least + // once. + grpListerSynced cache.InformerSynced + // addressGroupStore is the storage where the populated Address Groups are stored. addressGroupStore storage.Interface // appliedToGroupStore is the storage where the populated AppliedTo Groups are stored. @@ -290,6 +301,46 @@ var anpIndexers = cache.Indexers{ } return []string{anp.Spec.Tier}, nil }, + GroupIndex: func(obj interface{}) ([]string, error) { + anp, ok := obj.(*secv1alpha1.NetworkPolicy) + if !ok { + return []string{}, nil + } + ns := anp.Namespace + "/" + groupNames := sets.String{} + for _, appTo := range anp.Spec.AppliedTo { + if appTo.Group != "" { + groupNames.Insert(ns + appTo.Group) + } + } + if len(anp.Spec.Ingress) == 0 && len(anp.Spec.Egress) == 0 { + return groupNames.List(), nil + } + appendGroups := func(rule secv1alpha1.Rule) { + for _, peer := range rule.To { + if peer.Group != "" { + groupNames.Insert(ns + peer.Group) + } + } + for _, peer := range rule.From { + if peer.Group != "" { + groupNames.Insert(ns + peer.Group) + } + } + for _, appTo := range rule.AppliedTo { + if appTo.Group != "" { + groupNames.Insert(ns + appTo.Group) + } + } + } + for _, rule := range anp.Spec.Egress { + appendGroups(rule) + } + for _, rule := range anp.Spec.Ingress { + appendGroups(rule) + } + return groupNames.List(), nil + }, } // NewNetworkPolicyController returns a new *NetworkPolicyController. @@ -304,6 +355,7 @@ func NewNetworkPolicyController(kubeClient clientset.Interface, anpInformer secinformers.NetworkPolicyInformer, tierInformer secinformers.TierInformer, cgInformer crdv1a3informers.ClusterGroupInformer, + grpInformer crdv1a3informers.GroupInformer, addressGroupStore storage.Interface, appliedToGroupStore storage.Interface, internalNetworkPolicyStore storage.Interface, @@ -327,7 +379,7 @@ func NewNetworkPolicyController(kubeClient clientset.Interface, } n.groupingInterface.AddEventHandler(appliedToGroupType, n.enqueueAppliedToGroup) n.groupingInterface.AddEventHandler(addressGroupType, n.enqueueAddressGroup) - n.groupingInterface.AddEventHandler(clusterGroupType, n.enqueueInternalGroup) + n.groupingInterface.AddEventHandler(internalGroupType, n.enqueueInternalGroup) // Add handlers for NetworkPolicy events. networkPolicyInformer.Informer().AddEventHandlerWithResyncPeriod( cache.ResourceEventHandlerFuncs{ @@ -360,6 +412,9 @@ func NewNetworkPolicyController(kubeClient clientset.Interface, n.cgInformer = cgInformer n.cgLister = cgInformer.Lister() n.cgListerSynced = cgInformer.Informer().HasSynced + n.grpInformer = grpInformer + n.grpLister = grpInformer.Lister() + n.grpListerSynced = grpInformer.Informer().HasSynced // Add handlers for Namespace events. n.namespaceInformer.Informer().AddEventHandlerWithResyncPeriod( cache.ResourceEventHandlerFuncs{ @@ -414,6 +469,15 @@ func NewNetworkPolicyController(kubeClient clientset.Interface, }, resyncPeriod, ) + // Add event handlers for Group notification. + grpInformer.Informer().AddEventHandlerWithResyncPeriod( + cache.ResourceEventHandlerFuncs{ + AddFunc: n.addGroup, + UpdateFunc: n.updateGroup, + DeleteFunc: n.deleteGroup, + }, + resyncPeriod, + ) } return n } @@ -1107,7 +1171,7 @@ func (n *NetworkPolicyController) getAddressGroupMemberSet(g *antreatypes.Addres // childGroup with ipBlocks, this function only returns the aggregated GroupMemberSet // computed from childGroup with selectors, as ipBlocks will be processed differently. group := groupObj.(*antreatypes.Group) - members, _ := n.getClusterGroupMembers(group) + members, _ := n.getInternalGroupMembers(group) return members } if g.Selector.NodeSelector != nil { @@ -1116,22 +1180,23 @@ func (n *NetworkPolicyController) getAddressGroupMemberSet(g *antreatypes.Addres return n.getMemberSetForGroupType(addressGroupType, g.Name) } -// getClusterGroupMembers knows how to construct a GroupMemberSet and ipBlocks that contains -// all the entities selected by a ClusterGroup. For ClusterGroup that has childGroups, +// getInternalGroupMembers knows how to construct a GroupMemberSet and ipBlocks that contains +// all the entities selected by an internal Group. For internal Groups that has childGroups, // the members are computed as the union of all its childGroup's members. -func (n *NetworkPolicyController) getClusterGroupMembers(group *antreatypes.Group) (controlplane.GroupMemberSet, []controlplane.IPBlock) { +func (n *NetworkPolicyController) getInternalGroupMembers(group *antreatypes.Group) (controlplane.GroupMemberSet, []controlplane.IPBlock) { if len(group.IPBlocks) > 0 { return nil, group.IPBlocks } else if len(group.ChildGroups) == 0 { - return n.getMemberSetForGroupType(clusterGroupType, group.Name), nil + return n.getMemberSetForGroupType(internalGroupType, group.SourceReference.ToGroupName()), nil } var ipBlocks []controlplane.IPBlock groupMemberSet := controlplane.GroupMemberSet{} for _, childName := range group.ChildGroups { + childName = k8s.NamespacedName(group.SourceReference.Namespace, childName) childGroup, found, _ := n.internalGroupStore.Get(childName) if found { child := childGroup.(*antreatypes.Group) - members, ipb := n.getClusterGroupMembers(child) + members, ipb := n.getInternalGroupMembers(child) ipBlocks = append(ipBlocks, ipb...) groupMemberSet.Merge(members) } @@ -1344,22 +1409,24 @@ func (n *NetworkPolicyController) getAppliedToWorkloads(g *antreatypes.AppliedTo if found { // This AppliedToGroup is derived from a ClusterGroup. grp := group.(*antreatypes.Group) - return n.getClusterGroupWorkloads(grp) + return n.getInternalGroupWorkloads(grp) } return n.groupingInterface.GetEntities(appliedToGroupType, g.Name) } -// getClusterGroupWorkloads returns a list of workloads (Pods and ExternalEntities) selected by a ClusterGroup. +// getInternalGroupWorkloads returns a list of workloads (Pods and ExternalEntities) selected by a ClusterGroup. // For ClusterGroup that has childGroups, the workloads are computed as the union of all its childGroup's workloads. -func (n *NetworkPolicyController) getClusterGroupWorkloads(group *antreatypes.Group) ([]*v1.Pod, []*v1alpha2.ExternalEntity) { +func (n *NetworkPolicyController) getInternalGroupWorkloads(group *antreatypes.Group) ([]*v1.Pod, []*v1alpha2.ExternalEntity) { if len(group.ChildGroups) == 0 { - return n.groupingInterface.GetEntities(clusterGroupType, group.Name) + return n.groupingInterface.GetEntities(internalGroupType, group.SourceReference.ToGroupName()) } podNameSet, eeNameSet := sets.String{}, sets.String{} var pods []*v1.Pod var ees []*v1alpha2.ExternalEntity for _, childName := range group.ChildGroups { - childPods, childEEs := n.groupingInterface.GetEntities(clusterGroupType, childName) + // childNameString will either be name of the child ClusterGroup or Namespaced name of the child Group. + childNameString := k8s.NamespacedName(group.SourceReference.Namespace, childName) + childPods, childEEs := n.groupingInterface.GetEntities(internalGroupType, childNameString) for _, pod := range childPods { podString := k8s.NamespacedName(pod.Namespace, pod.Name) if !podNameSet.Has(podString) { @@ -1425,6 +1492,7 @@ func (n *NetworkPolicyController) syncInternalNetworkPolicy(key string) error { PerNamespaceSelectors: internalNP.PerNamespaceSelectors, SpanMeta: antreatypes.SpanMeta{NodeNames: nodeNames}, Generation: internalNP.Generation, + RealizationError: internalNP.RealizationError, } klog.V(4).Infof("Updating internal NetworkPolicy %s with %d Nodes", key, nodeNames.Len()) n.internalNetworkPolicyStore.Update(updatedNetworkPolicy) @@ -1483,7 +1551,11 @@ func internalNetworkPolicyKeyFunc(obj metav1.Object) string { } // internalGroupKeyFunc knows how to generate the key for an internal Group based on the object metadata -// of the corresponding ClusterGroup resource. Currently the Name of the ClusterGroup is used to ensure uniqueness. +// of the corresponding Group and ClusterGroup resource. Currently the Name of the ClusterGroup is used to ensure +// uniqueness. Similarly, the Namespaced Name of the Group is used to ensure uniqueness for the Group resource. func internalGroupKeyFunc(obj metav1.Object) string { + if len(obj.GetNamespace()) > 0 { + return obj.GetNamespace() + "/" + obj.GetName() + } return obj.GetName() } diff --git a/pkg/controller/networkpolicy/networkpolicy_controller_test.go b/pkg/controller/networkpolicy/networkpolicy_controller_test.go index 76a85c8a0c7..c3d9c2ed5b0 100644 --- a/pkg/controller/networkpolicy/networkpolicy_controller_test.go +++ b/pkg/controller/networkpolicy/networkpolicy_controller_test.go @@ -80,6 +80,7 @@ type networkPolicyController struct { cnpStore cache.Store tierStore cache.Store cgStore cache.Store + gStore cache.Store appliedToGroupStore storage.Interface addressGroupStore storage.Interface internalNetworkPolicyStore storage.Interface @@ -99,6 +100,7 @@ func newController(objects ...runtime.Object) (*fake.Clientset, *networkPolicyCo internalNetworkPolicyStore := store.NewNetworkPolicyStore() internalGroupStore := store.NewGroupStore() cgInformer := crdInformerFactory.Crd().V1alpha3().ClusterGroups() + gInformer := crdInformerFactory.Crd().V1alpha3().Groups() groupEntityIndex := grouping.NewGroupEntityIndex() groupingController := grouping.NewGroupEntityController(groupEntityIndex, informerFactory.Core().V1().Pods(), @@ -115,6 +117,7 @@ func newController(objects ...runtime.Object) (*fake.Clientset, *networkPolicyCo crdInformerFactory.Crd().V1alpha1().NetworkPolicies(), crdInformerFactory.Crd().V1alpha1().Tiers(), cgInformer, + gInformer, addressGroupStore, appliedToGroupStore, internalNetworkPolicyStore, @@ -138,6 +141,7 @@ func newController(objects ...runtime.Object) (*fake.Clientset, *networkPolicyCo crdInformerFactory.Crd().V1alpha1().ClusterNetworkPolicies().Informer().GetStore(), crdInformerFactory.Crd().V1alpha1().Tiers().Informer().GetStore(), crdInformerFactory.Crd().V1alpha3().ClusterGroups().Informer().GetStore(), + crdInformerFactory.Crd().V1alpha3().Groups().Informer().GetStore(), appliedToGroupStore, addressGroupStore, internalNetworkPolicyStore, @@ -164,6 +168,7 @@ func newControllerWithoutEventHandler(k8sObjects, crdObjects []runtime.Object) ( cnpInformer := crdInformerFactory.Crd().V1alpha1().ClusterNetworkPolicies() anpInformer := crdInformerFactory.Crd().V1alpha1().NetworkPolicies() cgInformer := crdInformerFactory.Crd().V1alpha3().ClusterGroups() + groupInformer := crdInformerFactory.Crd().V1alpha3().Groups() groupEntityIndex := grouping.NewGroupEntityIndex() npController := &NetworkPolicyController{ kubeClient: client, @@ -205,6 +210,7 @@ func newControllerWithoutEventHandler(k8sObjects, crdObjects []runtime.Object) ( cnpInformer.Informer().GetStore(), tierInformer.Informer().GetStore(), cgInformer.Informer().GetStore(), + groupInformer.Informer().GetStore(), appliedToGroupStore, addressGroupStore, internalNetworkPolicyStore, @@ -3100,6 +3106,16 @@ func TestInternalGroupKeyFunc(t *testing.T) { } actualValue := internalGroupKeyFunc(&cg) assert.Equal(t, expValue, actualValue) + + expValue = "nsA/gA" + g := v1alpha3.Group{ + ObjectMeta: metav1.ObjectMeta{Namespace: "nsA", Name: "gA", UID: "uid-a"}, + Spec: v1alpha3.GroupSpec{ + NamespaceSelector: &selectorA, + }, + } + actualValue = internalGroupKeyFunc(&g) + assert.Equal(t, expValue, actualValue) } func TestGetAppliedToWorkloads(t *testing.T) { diff --git a/pkg/controller/networkpolicy/status_controller.go b/pkg/controller/networkpolicy/status_controller.go index cb9fec00045..265489e6858 100644 --- a/pkg/controller/networkpolicy/status_controller.go +++ b/pkg/controller/networkpolicy/status_controller.go @@ -111,7 +111,7 @@ func NewStatusController(antreaClient antreaclientset.Interface, internalNetwork func (c *StatusController) updateCNP(old, cur interface{}) { curCNP := cur.(*crdv1alpha1.ClusterNetworkPolicy) oldCNP := old.(*crdv1alpha1.ClusterNetworkPolicy) - if oldCNP.Status == curCNP.Status { + if NetworkPolicyStatusEqual(oldCNP.Status, curCNP.Status) { return } key := internalNetworkPolicyKeyFunc(oldCNP) @@ -121,7 +121,7 @@ func (c *StatusController) updateCNP(old, cur interface{}) { func (c *StatusController) updateANP(old, cur interface{}) { curANP := cur.(*crdv1alpha1.NetworkPolicy) oldANP := old.(*crdv1alpha1.NetworkPolicy) - if oldANP.Status == curANP.Status { + if NetworkPolicyStatusEqual(oldANP.Status, curANP.Status) { return } key := internalNetworkPolicyKeyFunc(oldANP) @@ -274,12 +274,28 @@ func (c *StatusController) syncHandler(key string) error { status := &crdv1alpha1.NetworkPolicyStatus{ Phase: crdv1alpha1.NetworkPolicyPending, ObservedGeneration: internalNP.Generation, + Conditions: GenerateNetworkPolicyCondition(nil), } if internalNP.SourceRef.Type == controlplane.AntreaNetworkPolicy { return c.npControlInterface.UpdateAntreaNetworkPolicyStatus(internalNP.SourceRef.Namespace, internalNP.SourceRef.Name, status) } return c.npControlInterface.UpdateAntreaClusterNetworkPolicyStatus(internalNP.SourceRef.Name, status) } + + // It means the NetworkPolicy has been processed, and marked as unrealizable. It will enter unrealizable phase + // instead of being further realized. Antrea-agents will not process further. + if internalNP.RealizationError != nil { + status := &crdv1alpha1.NetworkPolicyStatus{ + Phase: crdv1alpha1.NetworkPolicyPending, + ObservedGeneration: internalNP.Generation, + Conditions: GenerateNetworkPolicyCondition(internalNP.RealizationError), + } + if internalNP.SourceRef.Type == controlplane.AntreaNetworkPolicy { + return c.npControlInterface.UpdateAntreaNetworkPolicyStatus(internalNP.SourceRef.Namespace, internalNP.SourceRef.Name, status) + } + return c.npControlInterface.UpdateAntreaClusterNetworkPolicyStatus(internalNP.SourceRef.Name, status) + } + desiredNodes := len(internalNP.SpanMeta.NodeNames) currentNodes := 0 statuses := c.getNodeStatuses(key) @@ -304,6 +320,7 @@ func (c *StatusController) syncHandler(key string) error { ObservedGeneration: internalNP.Generation, CurrentNodesRealized: int32(currentNodes), DesiredNodesRealized: int32(desiredNodes), + Conditions: GenerateNetworkPolicyCondition(nil), } klog.V(2).Infof("Updating NetworkPolicy %s status: %v", internalNP.SourceRef.ToString(), status) if internalNP.SourceRef.Type == controlplane.AntreaNetworkPolicy { @@ -331,7 +348,7 @@ func (c *networkPolicyControl) UpdateAntreaNetworkPolicyStatus(namespace, name s klog.Infof("Didn't find the original Antrea NetworkPolicy %s/%s, skip updating status", namespace, name) return nil } - if anp.Status == *status { + if NetworkPolicyStatusEqual(anp.Status, *status) { return nil } @@ -364,7 +381,7 @@ func (c *networkPolicyControl) UpdateAntreaClusterNetworkPolicyStatus(name strin return nil } // If the current status equals to the desired status, no need to update. - if cnp.Status == *status { + if NetworkPolicyStatusEqual(cnp.Status, *status) { return nil } @@ -389,3 +406,27 @@ func (c *networkPolicyControl) UpdateAntreaClusterNetworkPolicyStatus(name strin metrics.AntreaClusterNetworkPolicyStatusUpdates.Inc() return updateErr } + +// GenerateNetworkPolicyCondition generates conditions based on the given error type. +// Error of nil type means the NetworkPolicyCondition status is True. +// Supports ErrNetworkPolicyAppliedToUnsupportedGroup error. +func GenerateNetworkPolicyCondition(err error) []crdv1alpha1.NetworkPolicyCondition { + var conditions []crdv1alpha1.NetworkPolicyCondition + switch err.(type) { + case nil: + conditions = append(conditions, crdv1alpha1.NetworkPolicyCondition{ + Type: crdv1alpha1.NetworkPolicyConditionRealizable, + Status: v1.ConditionTrue, + LastTransitionTime: v1.Now(), + }) + case ErrNetworkPolicyAppliedToUnsupportedGroup: + conditions = append(conditions, crdv1alpha1.NetworkPolicyCondition{ + Type: crdv1alpha1.NetworkPolicyConditionRealizable, + Status: v1.ConditionFalse, + LastTransitionTime: v1.Now(), + Reason: "NetworkPolicyAppliedToUnsupportedGroup", + Message: err.Error(), + }) + } + return conditions +} diff --git a/pkg/controller/networkpolicy/status_controller_test.go b/pkg/controller/networkpolicy/status_controller_test.go index 32573986eae..913fc6b9527 100644 --- a/pkg/controller/networkpolicy/status_controller_test.go +++ b/pkg/controller/networkpolicy/status_controller_test.go @@ -163,12 +163,14 @@ func TestCreateAntreaNetworkPolicy(t *testing.T) { ObservedGeneration: 1, CurrentNodesRealized: 0, DesiredNodesRealized: 2, + Conditions: GenerateNetworkPolicyCondition(nil), }, expectedCNPStatus: &crdv1alpha1.NetworkPolicyStatus{ Phase: crdv1alpha1.NetworkPolicyRealizing, ObservedGeneration: 1, CurrentNodesRealized: 0, DesiredNodesRealized: 2, + Conditions: GenerateNetworkPolicyCondition(nil), }, }, { @@ -188,12 +190,14 @@ func TestCreateAntreaNetworkPolicy(t *testing.T) { ObservedGeneration: 2, CurrentNodesRealized: 1, DesiredNodesRealized: 2, + Conditions: GenerateNetworkPolicyCondition(nil), }, expectedCNPStatus: &crdv1alpha1.NetworkPolicyStatus{ Phase: crdv1alpha1.NetworkPolicyRealizing, ObservedGeneration: 3, CurrentNodesRealized: 1, DesiredNodesRealized: 2, + Conditions: GenerateNetworkPolicyCondition(nil), }, }, { @@ -213,12 +217,14 @@ func TestCreateAntreaNetworkPolicy(t *testing.T) { ObservedGeneration: 3, CurrentNodesRealized: 2, DesiredNodesRealized: 2, + Conditions: GenerateNetworkPolicyCondition(nil), }, expectedCNPStatus: &crdv1alpha1.NetworkPolicyStatus{ Phase: crdv1alpha1.NetworkPolicyRealized, ObservedGeneration: 4, CurrentNodesRealized: 2, DesiredNodesRealized: 2, + Conditions: GenerateNetworkPolicyCondition(nil), }, }, } @@ -243,8 +249,8 @@ func TestCreateAntreaNetworkPolicy(t *testing.T) { // TODO: Use a determinate mechanism. time.Sleep(500 * time.Millisecond) - assert.Equal(t, tt.expectedANPStatus, networkPolicyControl.getAntreaNetworkPolicyStatus()) - assert.Equal(t, tt.expectedCNPStatus, networkPolicyControl.getAntreaClusterNetworkPolicyStatus()) + assert.True(t, NetworkPolicyStatusEqual(*tt.expectedANPStatus, *networkPolicyControl.getAntreaNetworkPolicyStatus())) + assert.True(t, NetworkPolicyStatusEqual(*tt.expectedCNPStatus, *networkPolicyControl.getAntreaClusterNetworkPolicyStatus())) }) } } @@ -267,18 +273,20 @@ func TestUpdateAntreaNetworkPolicy(t *testing.T) { statusController.UpdateStatus(newNetworkPolicyStatus("cnp1", "node5", 2)) // TODO: Use a determinate mechanism. time.Sleep(500 * time.Millisecond) - assert.Equal(t, &crdv1alpha1.NetworkPolicyStatus{ + assert.True(t, NetworkPolicyStatusEqual(crdv1alpha1.NetworkPolicyStatus{ Phase: crdv1alpha1.NetworkPolicyRealized, ObservedGeneration: 1, CurrentNodesRealized: 2, DesiredNodesRealized: 2, - }, networkPolicyControl.getAntreaNetworkPolicyStatus()) - assert.Equal(t, &crdv1alpha1.NetworkPolicyStatus{ + Conditions: GenerateNetworkPolicyCondition(nil), + }, *networkPolicyControl.getAntreaNetworkPolicyStatus())) + assert.True(t, NetworkPolicyStatusEqual(crdv1alpha1.NetworkPolicyStatus{ Phase: crdv1alpha1.NetworkPolicyRealized, ObservedGeneration: 2, CurrentNodesRealized: 3, DesiredNodesRealized: 3, - }, networkPolicyControl.getAntreaClusterNetworkPolicyStatus()) + Conditions: GenerateNetworkPolicyCondition(nil), + }, *networkPolicyControl.getAntreaClusterNetworkPolicyStatus())) anp1Updated := newInternalNetworkPolicy("anp1", 2, []string{"node1", "node2", "node3"}, newAntreaNetworkPolicyReference("ns1", "anp1")) cnp1Updated := newInternalNetworkPolicy("cnp1", 3, []string{"node4", "node5"}, newAntreaClusterNetworkPolicyReference("cnp1")) @@ -286,18 +294,20 @@ func TestUpdateAntreaNetworkPolicy(t *testing.T) { networkPolicyStore.Update(cnp1Updated) // TODO: Use a determinate mechanism. time.Sleep(500 * time.Millisecond) - assert.Equal(t, &crdv1alpha1.NetworkPolicyStatus{ + assert.True(t, NetworkPolicyStatusEqual(crdv1alpha1.NetworkPolicyStatus{ Phase: crdv1alpha1.NetworkPolicyRealizing, ObservedGeneration: 2, CurrentNodesRealized: 0, DesiredNodesRealized: 3, - }, networkPolicyControl.getAntreaNetworkPolicyStatus()) - assert.Equal(t, &crdv1alpha1.NetworkPolicyStatus{ + Conditions: GenerateNetworkPolicyCondition(nil), + }, *networkPolicyControl.getAntreaNetworkPolicyStatus())) + assert.True(t, NetworkPolicyStatusEqual(crdv1alpha1.NetworkPolicyStatus{ Phase: crdv1alpha1.NetworkPolicyRealizing, ObservedGeneration: 3, CurrentNodesRealized: 0, DesiredNodesRealized: 2, - }, networkPolicyControl.getAntreaClusterNetworkPolicyStatus()) + Conditions: GenerateNetworkPolicyCondition(nil), + }, *networkPolicyControl.getAntreaClusterNetworkPolicyStatus())) } func TestDeleteAntreaNetworkPolicy(t *testing.T) { diff --git a/pkg/controller/networkpolicy/store/group.go b/pkg/controller/networkpolicy/store/group.go index d8316592660..6083622c3af 100644 --- a/pkg/controller/networkpolicy/store/group.go +++ b/pkg/controller/networkpolicy/store/group.go @@ -31,14 +31,13 @@ const ( ChildGroupIndex = "childGroup" ) -// GroupKeyFunc knows how to get the key of an Group. +// GroupKeyFunc knows how to get the key of a Group. func GroupKeyFunc(obj interface{}) (string, error) { group, ok := obj.(*antreatypes.Group) if !ok { return "", fmt.Errorf("object is not *types.Group: %v", obj) } - // Replace empty Namespace with Group.Namespace once Namespaced Groups are introduced. - return k8s.NamespacedName("", group.Name), nil + return k8s.NamespacedName(group.SourceReference.Namespace, group.SourceReference.Name), nil } // NewGroupStore creates a store of Group. @@ -63,6 +62,13 @@ func NewGroupStore() storage.Interface { if !ok { return []string{}, nil } + if g.SourceReference.Namespace != "" { + namespacedCG := make([]string, len(g.ChildGroups)) + for _, childGroup := range g.ChildGroups { + namespacedCG = append(namespacedCG, g.SourceReference.Namespace+"/"+childGroup) + } + return namespacedCG, nil + } return g.ChildGroups, nil }, } diff --git a/pkg/controller/networkpolicy/validate.go b/pkg/controller/networkpolicy/validate.go index 33778b48bbc..713e1318f1c 100644 --- a/pkg/controller/networkpolicy/validate.go +++ b/pkg/controller/networkpolicy/validate.go @@ -34,6 +34,7 @@ import ( crdv1alpha1 "antrea.io/antrea/pkg/apis/crd/v1alpha1" crdv1alpha2 "antrea.io/antrea/pkg/apis/crd/v1alpha2" + crdv1alpha3 "antrea.io/antrea/pkg/apis/crd/v1alpha3" "antrea.io/antrea/pkg/controller/networkpolicy/store" "antrea.io/antrea/pkg/features" "antrea.io/antrea/pkg/util/env" @@ -144,7 +145,7 @@ func NewNetworkPolicyValidator(networkPolicyController *NetworkPolicyController) return &vr } -// Validate function validates a ClusterGroup, Tier or Antrea Policy object +// Validate function validates a Group, ClusterGroup, Tier or Antrea Policy object func (v *NetworkPolicyValidator) Validate(ar *admv1.AdmissionReview) *admv1.AdmissionResponse { var result *metav1.Status var msg string @@ -186,6 +187,22 @@ func (v *NetworkPolicyValidator) Validate(ar *admv1.AdmissionReview) *admv1.Admi } } msg, allowed = v.validateAntreaGroup(&curCG, &oldCG, op, ui) + case "Group": + klog.V(2).Info("Validating Group CRD") + var curG, oldG crdv1alpha3.Group + if curRaw != nil { + if err := json.Unmarshal(curRaw, &curG); err != nil { + klog.Errorf("Error de-serializing current Group") + return GetAdmissionResponseForErr(err) + } + } + if oldRaw != nil { + if err := json.Unmarshal(oldRaw, &oldG); err != nil { + klog.Errorf("Error de-serializing old Group") + return GetAdmissionResponseForErr(err) + } + } + msg, allowed = v.validateAntreaGroup(&curG, &oldG, op, ui) case "ClusterNetworkPolicy": klog.V(2).Info("Validating Antrea ClusterNetworkPolicy CRD") var curCNP, oldCNP crdv1alpha1.ClusterNetworkPolicy @@ -292,31 +309,31 @@ func (v *antreaPolicyValidator) validatePort(ingress, egress []crdv1alpha1.Rule) return nil } -// validateAntreaGroup validates the admission of a ClusterGroup resource -func (v *NetworkPolicyValidator) validateAntreaGroup(curCG, oldCG *crdv1alpha2.ClusterGroup, op admv1.Operation, userInfo authenticationv1.UserInfo) (string, bool) { +// validateAntreaGroup validates the admission of a Group, ClusterGroup resource +func (v *NetworkPolicyValidator) validateAntreaGroup(curAG, oldAG interface{}, op admv1.Operation, userInfo authenticationv1.UserInfo) (string, bool) { allowed := true reason := "" switch op { case admv1.Create: - klog.V(2).Info("Validating CREATE request for ClusterGroup") + klog.V(2).Info("Validating CREATE request for ClusterGroup/Group") for _, val := range v.groupValidators { - reason, allowed = val.createValidate(curCG, userInfo) + reason, allowed = val.createValidate(curAG, userInfo) if !allowed { return reason, allowed } } case admv1.Update: - klog.V(2).Info("Validating UPDATE request for ClusterGroup") + klog.V(2).Info("Validating UPDATE request for ClusterGroup/Group") for _, val := range v.groupValidators { - reason, allowed = val.updateValidate(curCG, oldCG, userInfo) + reason, allowed = val.updateValidate(curAG, oldAG, userInfo) if !allowed { return reason, allowed } } case admv1.Delete: - klog.V(2).Info("Validating DELETE request for ClusterGroup") + klog.V(2).Info("Validating DELETE request for ClusterGroup/Group") for _, val := range v.groupValidators { - reason, allowed = val.deleteValidate(oldCG, userInfo) + reason, allowed = val.deleteValidate(oldAG, userInfo) if !allowed { return reason, allowed } @@ -867,9 +884,9 @@ func (t *tierValidator) deleteValidate(oldObj interface{}, userInfo authenticati return "", true } -// validateAntreaGroupSpec ensures that an IPBlock is not set along with namespaceSelector and/or a +// validateAntreaClusterGroupSpec ensures that an IPBlock is not set along with namespaceSelector and/or a // podSelector. Similarly, ExternalEntitySelector cannot be set with PodSelector. -func validateAntreaGroupSpec(s crdv1alpha2.GroupSpec) (string, bool) { +func validateAntreaClusterGroupSpec(s crdv1alpha2.GroupSpec) (string, bool) { errMsg := "At most one of podSelector, externalEntitySelector, serviceReference, ipBlock, ipBlocks or childGroups can be set for a ClusterGroup" if s.PodSelector != nil && s.ExternalEntitySelector != nil { return errMsg, false @@ -915,7 +932,31 @@ func validateAntreaGroupSpec(s crdv1alpha2.GroupSpec) (string, bool) { return "", true } -func (g *groupValidator) validateChildGroup(s *crdv1alpha2.ClusterGroup) (string, bool) { +func validateAntreaGroupSpec(s crdv1alpha3.GroupSpec) (string, bool) { + errMsg := "At most one of podSelector, externalEntitySelector, serviceReference, ipBlocks or childGroups can be set for a Group" + if s.PodSelector != nil && s.ExternalEntitySelector != nil { + return errMsg, false + } + selector, serviceRef, ipBlocks, childGroups := 0, 0, 0, 0 + if s.NamespaceSelector != nil || s.ExternalEntitySelector != nil || s.PodSelector != nil { + selector = 1 + } + if len(s.IPBlocks) > 0 { + ipBlocks = 1 + } + if s.ServiceReference != nil { + serviceRef = 1 + } + if len(s.ChildGroups) > 0 { + childGroups = 1 + } + if selector+serviceRef+ipBlocks+childGroups > 1 { + return errMsg, false + } + return "", true +} + +func (g *groupValidator) validateChildClusterGroup(s *crdv1alpha2.ClusterGroup) (string, bool) { if len(s.Spec.ChildGroups) > 0 { parentGrps, err := g.networkPolicyController.internalGroupStore.GetByIndex(store.ChildGroupIndex, s.Name) if err != nil { @@ -940,27 +981,82 @@ func (g *groupValidator) validateChildGroup(s *crdv1alpha2.ClusterGroup) (string return "", true } -// createValidate validates the CREATE events of ClusterGroup resources. -func (g *groupValidator) createValidate(curObj interface{}, userInfo authenticationv1.UserInfo) (string, bool) { - curCG := curObj.(*crdv1alpha2.ClusterGroup) - reason, allowed := validateAntreaGroupSpec(curCG.Spec) +func (g *groupValidator) validateChildGroup(s *crdv1alpha3.Group) (string, bool) { + if len(s.Spec.ChildGroups) > 0 { + parentGrps, err := g.networkPolicyController.internalGroupStore.GetByIndex(store.ChildGroupIndex, s.Namespace+"/"+s.Name) + if err != nil { + return fmt.Sprintf("error retrieving parents of Group %s/%s: %v", s.Namespace, s.Name, err), false + } + // TODO: relax this constraint when max group nesting level increases. + if len(parentGrps) > 0 { + return fmt.Sprintf("cannot set childGroups for Group %s/%s, who has %d parents", s.Namespace, s.Name, len(parentGrps)), false + } + for _, groupname := range s.Spec.ChildGroups { + childGrp, err := g.networkPolicyController.grpLister.Groups(s.Namespace).Get(string(groupname)) + if err != nil { + // the childGroup has not been created yet. + continue + } + // TODO: relax this constraint when max group nesting level increases. + if len(childGrp.Spec.ChildGroups) > 0 { + return fmt.Sprintf("cannot set Group %s/%s as childGroup, who has %d childGroups itself", s.Namespace, string(groupname), len(childGrp.Spec.ChildGroups)), false + } + } + } + return "", true +} + +func (g *groupValidator) validateCG(cg *crdv1alpha2.ClusterGroup) (string, bool) { + reason, allowed := validateAntreaClusterGroupSpec(cg.Spec) if !allowed { return reason, allowed } - return g.validateChildGroup(curCG) + return g.validateChildClusterGroup(cg) } -// updateValidate validates the UPDATE events of ClusterGroup resources. -func (g *groupValidator) updateValidate(curObj, oldObj interface{}, userInfo authenticationv1.UserInfo) (string, bool) { - curCG := curObj.(*crdv1alpha2.ClusterGroup) - reason, allowed := validateAntreaGroupSpec(curCG.Spec) +func (g *groupValidator) validateG(grp *crdv1alpha3.Group) (string, bool) { + reason, allowed := validateAntreaGroupSpec(grp.Spec) if !allowed { return reason, allowed } - return g.validateChildGroup(curCG) + return g.validateChildGroup(grp) +} + +// createValidate validates the CREATE events of Group, ClusterGroup resources. +func (g *groupValidator) createValidate(curObj interface{}, userInfo authenticationv1.UserInfo) (string, bool) { + var curCG *crdv1alpha2.ClusterGroup + var curG *crdv1alpha3.Group + var reason string + var allowed bool + switch curObj.(type) { + case *crdv1alpha2.ClusterGroup: + curCG = curObj.(*crdv1alpha2.ClusterGroup) + reason, allowed = g.validateCG(curCG) + case *crdv1alpha3.Group: + curG = curObj.(*crdv1alpha3.Group) + reason, allowed = g.validateG(curG) + } + return reason, allowed +} + +// updateValidate validates the UPDATE events of Group, ClusterGroup resources. +func (g *groupValidator) updateValidate(curObj, oldObj interface{}, userInfo authenticationv1.UserInfo) (string, bool) { + var curCG *crdv1alpha2.ClusterGroup + var curG *crdv1alpha3.Group + var reason string + var allowed bool + switch curObj.(type) { + case *crdv1alpha2.ClusterGroup: + curCG = curObj.(*crdv1alpha2.ClusterGroup) + reason, allowed = g.validateCG(curCG) + case *crdv1alpha3.Group: + curG = curObj.(*crdv1alpha3.Group) + reason, allowed = g.validateG(curG) + } + return reason, allowed } -// deleteValidate validates the DELETE events of ClusterGroup resources. +// deleteValidate validates the DELETE events of Group, ClusterGroup resources. func (g *groupValidator) deleteValidate(oldObj interface{}, userInfo authenticationv1.UserInfo) (string, bool) { return "", true } diff --git a/pkg/controller/networkpolicy/validate_test.go b/pkg/controller/networkpolicy/validate_test.go index 2ac1dc93737..020d31dd9e4 100644 --- a/pkg/controller/networkpolicy/validate_test.go +++ b/pkg/controller/networkpolicy/validate_test.go @@ -24,6 +24,7 @@ import ( crdv1alpha1 "antrea.io/antrea/pkg/apis/crd/v1alpha1" crdv1alpha2 "antrea.io/antrea/pkg/apis/crd/v1alpha2" + crdv1alpha3 "antrea.io/antrea/pkg/apis/crd/v1alpha3" ) func TestValidateAntreaPolicy(t *testing.T) { @@ -1239,7 +1240,7 @@ func TestValidateAntreaPolicy(t *testing.T) { } } -func TestValidateClusterAntreaGroup(t *testing.T) { +func TestValidateAntreaClusterGroup(t *testing.T) { tests := []struct { name string group *crdv1alpha2.ClusterGroup @@ -1288,3 +1289,43 @@ func TestValidateClusterAntreaGroup(t *testing.T) { }) } } + +func TestValidateAntreaGroup(t *testing.T) { + tests := []struct { + name string + group *crdv1alpha3.Group + expectedReason string + }{ + { + name: "anp-group-set-with-podselector-and-ipblock", + group: &crdv1alpha3.Group{ + ObjectMeta: metav1.ObjectMeta{ + Name: "anp-group-set-with-podselector-and-ipblock", + Namespace: "x", + }, + Spec: crdv1alpha3.GroupSpec{ + PodSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"foo=": "bar"}, + }, + IPBlocks: []crdv1alpha1.IPBlock{ + {CIDR: "10.0.0.10/32"}, + }, + }, + }, + expectedReason: "At most one of podSelector, externalEntitySelector, serviceReference, ipBlocks or childGroups can be set for a Group", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, c := newController() + v := NewNetworkPolicyValidator(c.NetworkPolicyController) + actualReason, allowed := v.validateAntreaGroup(tt.group, nil, admv1.Create, authenticationv1.UserInfo{}) + assert.Equal(t, tt.expectedReason, actualReason) + if tt.expectedReason == "" { + assert.True(t, allowed) + } else { + assert.False(t, allowed) + } + }) + } +} diff --git a/pkg/controller/types/group.go b/pkg/controller/types/group.go index 159f7a7d1bc..ae43b687559 100644 --- a/pkg/controller/types/group.go +++ b/pkg/controller/types/group.go @@ -115,8 +115,8 @@ type Group struct { // UID is a unique identifier of this internal Group. It is same as that of the ClusterGroup // resource UID. UID types.UID - // Name of the ClusterGroup for which this internal Group is created. - Name string + // Reference of the ClusterGroup/Group for which this internal Group is created. + SourceReference *controlplane.GroupReference // MembersComputed knows whether the controller has computed the comprehensive members // of the Group. It is updated during the syncInternalGroup process. MembersComputed v1.ConditionStatus @@ -128,6 +128,6 @@ type Group struct { // ServiceReference is reference to a v1.Service, which this Group keeps in sync // and updates Selector based on the Service's selector. ServiceReference *controlplane.ServiceReference - // ChildGroups is the list of Group names that belongs to this Group. + // ChildGroups is the list of Group names that belong to this Group. ChildGroups []string } diff --git a/pkg/controller/types/networkpolicy.go b/pkg/controller/types/networkpolicy.go index f4e34692208..be8c5ddc208 100644 --- a/pkg/controller/types/networkpolicy.go +++ b/pkg/controller/types/networkpolicy.go @@ -108,4 +108,7 @@ type NetworkPolicy struct { // to re-calculate affected Namespaces. // It is set only for AntreaClusterNetworkPolicies with per-namespace rules. PerNamespaceSelectors []labels.Selector + // RealizationError stores realization error of the internal Network Policy. + // It is set when processing the original Network Policy. + RealizationError error } diff --git a/test/e2e/antreapolicy_test.go b/test/e2e/antreapolicy_test.go index 36e548a3f12..1c8876afd9f 100644 --- a/test/e2e/antreapolicy_test.go +++ b/test/e2e/antreapolicy_test.go @@ -253,7 +253,7 @@ func testMutateANPNoRuleName(t *testing.T) { SetAppliedToGroup([]ANPAppliedToSpec{{PodSelector: map[string]string{"pod": "a"}}}). SetPriority(10.0). AddIngress(ProtocolTCP, &p80, nil, nil, nil, nil, nil, nil, nil, map[string]string{"pod": "b"}, map[string]string{"ns": namespaces["x"]}, - nil, nil, nil, crdv1alpha1.RuleActionAllow, "") + nil, nil, nil, crdv1alpha1.RuleActionAllow, "", "") anp := builder.Get() log.Debugf("creating ANP %v", anp.Name) anp, err := k8sUtils.CreateOrUpdateANP(anp) @@ -284,6 +284,51 @@ func testInvalidACNPNoPriority(t *testing.T) { } } +func testInvalidANPIngressPeerGroupSetWithPodSelector(t *testing.T) { + gA := "gA" + namespace := "x" + selectorA := metav1.LabelSelector{MatchLabels: map[string]string{"foo1": "bar1"}} + ruleAppTo := ANPAppliedToSpec{ + PodSelector: map[string]string{"pod": "b"}, + } + k8sUtils.CreateGroup(namespace, gA, &selectorA, nil, nil) + invalidNpErr := fmt.Errorf("invalid Antrea NetworkPolicy with group and podSelector in NetworkPolicyPeer set") + builder := &AntreaNetworkPolicySpecBuilder{} + builder = builder.SetName(namespace, "anp-ingress-group-podselector-set"). + SetPriority(1.0) + builder = builder.AddIngress(ProtocolTCP, &p80, nil, nil, nil, nil, nil, nil, nil, map[string]string{"pod": "b"}, nil, + nil, nil, []ANPAppliedToSpec{ruleAppTo}, crdv1alpha1.RuleActionAllow, gA, "") + anp := builder.Get() + log.Debugf("creating ANP %v", anp.Name) + if _, err := k8sUtils.CreateOrUpdateANP(anp); err == nil { + // Above creation of ANP must fail as it is an invalid spec. + failOnError(invalidNpErr, t) + } + failOnError(k8sUtils.CleanGroups(namespace), t) +} + +func testInvalidANPIngressPeerGroupSetWithIPBlock(t *testing.T) { + gA := "gA" + namespace := "x" + selectorA := metav1.LabelSelector{MatchLabels: map[string]string{"foo1": "bar1"}} + k8sUtils.CreateGroup(namespace, gA, &selectorA, nil, nil) + invalidNpErr := fmt.Errorf("invalid Antrea NetworkPolicy with group and ipBlock in NetworkPolicyPeer set") + cidr := "10.0.0.10/32" + builder := &AntreaNetworkPolicySpecBuilder{} + builder = builder.SetName(namespace, "anp-ingress-group-ipblock-set"). + SetPriority(1.0). + SetAppliedToGroup([]ANPAppliedToSpec{{Group: "gA"}}) + builder = builder.AddIngress(ProtocolTCP, &p80, nil, nil, nil, nil, nil, nil, &cidr, map[string]string{"pod": "b"}, map[string]string{"ns": "x"}, + nil, nil, nil, crdv1alpha1.RuleActionAllow, gA, "") + anp := builder.Get() + log.Debugf("creating ANP %v", anp.Name) + if _, err := k8sUtils.CreateOrUpdateANP(anp); err == nil { + // Above creation of ANP must fail as it is an invalid spec. + failOnError(invalidNpErr, t) + } + failOnError(k8sUtils.CleanGroups(namespace), t) +} + func testInvalidANPNoPriority(t *testing.T) { invalidNpErr := fmt.Errorf("invalid Antrea NetworkPolicy without a priority accepted") builder := &AntreaNetworkPolicySpecBuilder{} @@ -303,9 +348,9 @@ func testInvalidANPRuleNameNotUnique(t *testing.T) { builder = builder.SetName(namespaces["x"], "anp-rule-name-not-unique"). SetAppliedToGroup([]ANPAppliedToSpec{{PodSelector: map[string]string{"pod": "a"}}}). AddIngress(ProtocolTCP, &p80, nil, nil, nil, nil, nil, nil, nil, map[string]string{"pod": "b"}, map[string]string{"ns": namespaces["x"]}, - nil, nil, nil, crdv1alpha1.RuleActionAllow, "not-unique"). + nil, nil, nil, crdv1alpha1.RuleActionAllow, "", "not-unique"). AddIngress(ProtocolTCP, &p81, nil, nil, nil, nil, nil, nil, nil, map[string]string{"pod": "c"}, map[string]string{"ns": namespaces["x"]}, - nil, nil, nil, crdv1alpha1.RuleActionAllow, "not-unique") + nil, nil, nil, crdv1alpha1.RuleActionAllow, "", "not-unique") anp := builder.Get() log.Debugf("creating ANP %v", anp.Name) if _, err := k8sUtils.CreateOrUpdateANP(anp); err == nil { @@ -335,7 +380,7 @@ func testInvalidANPPortRangePortUnset(t *testing.T) { SetPriority(1.0). SetAppliedToGroup([]ANPAppliedToSpec{{PodSelector: map[string]string{"pod": "b"}}}) builder.AddEgress(ProtocolTCP, nil, nil, &p8085, nil, nil, nil, nil, nil, map[string]string{"pod": "c"}, map[string]string{"ns": namespaces["x"]}, - nil, nil, nil, crdv1alpha1.RuleActionDrop, "anp-port-range") + nil, nil, nil, crdv1alpha1.RuleActionDrop, "", "anp-port-range") anp := builder.Get() log.Debugf("creating ANP %v", anp.Name) @@ -352,7 +397,7 @@ func testInvalidANPPortRangeEndPortSmall(t *testing.T) { SetPriority(1.0). SetAppliedToGroup([]ANPAppliedToSpec{{PodSelector: map[string]string{"pod": "b"}}}) builder.AddEgress(ProtocolTCP, &p8082, nil, &p8081, nil, nil, nil, nil, nil, map[string]string{"pod": "c"}, map[string]string{"ns": namespaces["x"]}, - nil, nil, nil, crdv1alpha1.RuleActionDrop, "anp-port-range") + nil, nil, nil, crdv1alpha1.RuleActionDrop, "", "anp-port-range") anp := builder.Get() log.Debugf("creating ANP %v", anp.Name) @@ -438,14 +483,14 @@ func testInvalidTierACNPRefDelete(t *testing.T) { func testInvalidTierANPRefDelete(t *testing.T) { invalidErr := fmt.Errorf("tier deleted with referenced ANPs") - tr, err := k8sUtils.CreateNewTier("tier-anp", 10) + tr, err := k8sUtils.CreateNewTier("tier-anp-ref", 11) if err != nil { failOnError(fmt.Errorf("create Tier failed for tier tier-anp: %v", err), t) } builder := &AntreaNetworkPolicySpecBuilder{} builder = builder.SetName(namespaces["x"], "anp-for-tier"). SetAppliedToGroup([]ANPAppliedToSpec{{PodSelector: map[string]string{"pod": "a"}}}). - SetTier("tier-anp"). + SetTier("tier-anp-ref"). SetPriority(13.0) anp := builder.Get() log.Debugf("creating ANP %v", anp.Name) @@ -1162,6 +1207,569 @@ func testACNPClusterGroupRefRuleIPBlocks(t *testing.T) { executeTests(t, testCase) } +// testANPEgressRulePodsAToGrpWithPodsC tests that an ANP is able to drop egress traffic from x/a to x/c. +func testANPEgressRulePodsAToGrpWithPodsC(t *testing.T) { + grpName := "grp-xc" + grpBuilder := &GroupSpecBuilder{} + grpBuilder = grpBuilder.SetName(grpName).SetNamespace(namespaces["x"]).SetPodSelector(map[string]string{"pod": "c"}, nil) + builder := &AntreaNetworkPolicySpecBuilder{} + builder = builder.SetName(namespaces["x"], "anp-deny-xa-to-grp-xc-egress"). + SetPriority(1.0). + SetAppliedToGroup([]ANPAppliedToSpec{{PodSelector: map[string]string{"pod": "a"}}}) + builder.AddEgress(ProtocolTCP, &p80, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, crdv1alpha1.RuleActionDrop, grpName, "") + + reachability := NewReachability(allPods, Connected) + reachability.Expect(Pod(namespaces["x"]+"/a"), Pod(namespaces["x"]+"/c"), Dropped) + testStep := []*TestStep{ + { + "Port 80", + reachability, + // Note in this testcase the Group is created after the ANP + []metav1.Object{builder.Get(), grpBuilder.Get()}, + []int32{80}, + ProtocolTCP, + 0, + nil, + }, + } + testCase := []*TestCase{ + {"ANP Drop Egress From All Pod:x/a to Group with Pod:x/c", testStep}, + } + executeTests(t, testCase) +} + +// testANPIngressRuleDenyGrpWithXCtoXA tests traffic from Group with X/B to X/A on named port 81 is dropped. +func testANPIngressRuleDenyGrpWithXCtoXA(t *testing.T) { + grpName := "grp-pods-xb" + grpBuilder := &GroupSpecBuilder{} + grpBuilder = grpBuilder.SetName(grpName).SetNamespace(namespaces["x"]).SetPodSelector(map[string]string{"pod": "b"}, nil) + port81Name := "serve-81" + builder := &AntreaNetworkPolicySpecBuilder{} + builder = builder.SetName(namespaces["x"], "anp-deny-grp-with-xb-to-xa"). + SetPriority(2.0). + SetAppliedToGroup([]ANPAppliedToSpec{{PodSelector: map[string]string{"pod": "a"}}}) + builder.AddIngress(ProtocolTCP, nil, &port81Name, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, crdv1alpha1.RuleActionDrop, grpName, "") + + reachability := NewReachability(allPods, Connected) + reachability.Expect(Pod(namespaces["x"]+"/b"), Pod(namespaces["x"]+"/a"), Dropped) + reachability.ExpectSelf(allPods, Connected) + + testStep := []*TestStep{ + { + "NamedPort 81", + reachability, + []metav1.Object{grpBuilder.Get(), builder.Get()}, + []int32{81}, + ProtocolTCP, + 0, + nil, + }, + } + testCase := []*TestCase{ + {"ANP Deny Group X/B to X/A", testStep}, + } + executeTests(t, testCase) +} + +func testANPGroupUpdate(t *testing.T) { + grpName := "grp-pod-xc-then-pod-xb" + grpBuilder := &GroupSpecBuilder{} + grpBuilder = grpBuilder.SetName(grpName).SetNamespace(namespaces["x"]).SetPodSelector(map[string]string{"pod": "c"}, nil) + // Update Group Pod selector from X/C to X/B + updatedGrpBuilder := &GroupSpecBuilder{} + updatedGrpBuilder = updatedGrpBuilder.SetName(grpName).SetNamespace(namespaces["x"]).SetPodSelector(map[string]string{"pod": "b"}, nil) + builder := &AntreaNetworkPolicySpecBuilder{} + builder = builder.SetName(namespaces["x"], "anp-deny-xa-to-grp-with-xc-egress"). + SetPriority(1.0). + SetAppliedToGroup([]ANPAppliedToSpec{{PodSelector: map[string]string{"pod": "a"}}}) + builder.AddEgress(ProtocolTCP, &p80, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, crdv1alpha1.RuleActionDrop, grpName, "") + + reachability := NewReachability(allPods, Connected) + reachability.Expect(Pod(namespaces["x"]+"/a"), Pod(namespaces["x"]+"/c"), Dropped) + + updatedReachability := NewReachability(allPods, Connected) + updatedReachability.Expect(Pod(namespaces["x"]+"/a"), Pod(namespaces["x"]+"/b"), Dropped) + testStep := []*TestStep{ + { + "Port 80", + reachability, + []metav1.Object{grpBuilder.Get(), builder.Get()}, + []int32{80}, + ProtocolTCP, + 0, + nil, + }, + { + "Port 80 - update", + updatedReachability, + []metav1.Object{updatedGrpBuilder.Get()}, + []int32{80}, + ProtocolTCP, + 0, + nil, + }, + } + testCase := []*TestCase{ + {"ANP Drop Egress From All Pod:x/a to Group with Pod:x/c updated to Group with Pod:x/b", testStep}, + } + executeTests(t, testCase) +} + +// testANPAppliedToDenyXBtoGrpWithXA tests traffic from X/B to Group X/A on named port 81 is dropped. +func testANPAppliedToDenyXBtoGrpWithXA(t *testing.T) { + grpName := "grp-pods-ya" + grpBuilder := &GroupSpecBuilder{} + grpBuilder = grpBuilder.SetName(grpName).SetNamespace(namespaces["x"]).SetPodSelector(map[string]string{"pod": "a"}, nil) + port81Name := "serve-81" + builder := &AntreaNetworkPolicySpecBuilder{} + builder = builder.SetName(namespaces["x"], "anp-deny-grp-with-xa-from-xb"). + SetPriority(2.0). + SetAppliedToGroup([]ANPAppliedToSpec{{Group: grpName}}) + builder.AddIngress(ProtocolTCP, nil, &port81Name, nil, nil, nil, nil, nil, nil, map[string]string{"pod": "b"}, nil, + nil, nil, nil, crdv1alpha1.RuleActionDrop, "", "") + + reachability := NewReachability(allPods, Connected) + reachability.Expect(Pod(namespaces["x"]+"/b"), Pod(namespaces["x"]+"/a"), Dropped) + reachability.ExpectSelf(allPods, Connected) + + testStep := []*TestStep{ + { + "NamedPort 81", + reachability, + // Note in this testcase the Group is created after the ANP + []metav1.Object{builder.Get(), grpBuilder.Get()}, + []int32{81}, + ProtocolTCP, + 0, + nil, + }, + } + testCase := []*TestCase{ + {"ANP Deny Group X/A from X/B", testStep}, + } + executeTests(t, testCase) +} + +// testANPAppliedToRuleGrpWithPodsAToPodsC tests that an ANP is able to drop egress traffic from GRP with pods labelled A to pods C. +func testANPAppliedToRuleGrpWithPodsAToPodsC(t *testing.T) { + grpName := "grp-pods-a" + grpBuilder := &GroupSpecBuilder{} + grpBuilder = grpBuilder.SetName(grpName).SetNamespace(namespaces["x"]).SetPodSelector(map[string]string{"pod": "a"}, nil) + builder := &AntreaNetworkPolicySpecBuilder{} + builder = builder.SetName(namespaces["x"], "anp-deny-grp-with-a-to-c"). + SetPriority(1.0) + builder.AddEgress(ProtocolTCP, &p80, nil, nil, nil, nil, nil, nil, nil, map[string]string{"pod": "c"}, nil, + nil, nil, []ANPAppliedToSpec{{Group: grpName}}, crdv1alpha1.RuleActionDrop, "", "") + + reachability := NewReachability(allPods, Connected) + reachability.Expect(Pod(namespaces["x"]+"/a"), Pod(namespaces["x"]+"/c"), Dropped) + testStep := []*TestStep{ + { + "Port 80", + reachability, + // Note in this testcase the Group is created after the ANP + []metav1.Object{builder.Get(), grpBuilder.Get()}, + []int32{80}, + ProtocolTCP, + 0, + nil, + }, + } + testCase := []*TestCase{ + {"ANP Drop Egress From Group with All Pod:a to Pod:c", testStep}, + } + executeTests(t, testCase) +} + +func testANPGroupUpdateAppliedTo(t *testing.T) { + grpName := "grp-pods-xa-then-xb" + grpBuilder := &GroupSpecBuilder{} + grpBuilder = grpBuilder.SetName(grpName).SetNamespace(namespaces["x"]).SetPodSelector(map[string]string{"pod": "a"}, nil) + // Update GRP Pod selector to group Pods x/b + updatedGrpBuilder := &GroupSpecBuilder{} + updatedGrpBuilder = updatedGrpBuilder.SetName(grpName).SetNamespace(namespaces["x"]).SetPodSelector(map[string]string{"pod": "b"}, nil) + builder := &AntreaNetworkPolicySpecBuilder{} + builder = builder.SetName(namespaces["x"], "anp-deny-grp-xc-to-xa-egress"). + SetPriority(1.0). + SetAppliedToGroup([]ANPAppliedToSpec{{Group: grpName}}) + builder.AddEgress(ProtocolTCP, &p80, nil, nil, nil, nil, nil, nil, nil, map[string]string{"pod": "c"}, nil, + nil, nil, nil, crdv1alpha1.RuleActionDrop, "", "") + + reachability := NewReachability(allPods, Connected) + reachability.Expect(Pod(namespaces["x"]+"/a"), Pod(namespaces["x"]+"/c"), Dropped) + + updatedReachability := NewReachability(allPods, Connected) + updatedReachability.Expect(Pod(namespaces["x"]+"/b"), Pod(namespaces["x"]+"/c"), Dropped) + testStep := []*TestStep{ + { + "GRP Pods X/C", + reachability, + []metav1.Object{grpBuilder.Get(), builder.Get()}, + []int32{80}, + ProtocolTCP, + 0, + nil, + }, + { + "GRP Pods X/B - update", + updatedReachability, + []metav1.Object{updatedGrpBuilder.Get()}, + []int32{80}, + ProtocolTCP, + 0, + nil, + }, + } + testCase := []*TestCase{ + {"ANP Drop Egress From Pod:x/c to Group Pod:x/a updated to Group with Pod:x/b", testStep}, + } + executeTests(t, testCase) +} + +func testANPGroupAppliedToPodAdd(t *testing.T, data *TestData) { + grpName := "grp-pod-custom-pod-xj" + grpBuilder := &GroupSpecBuilder{} + grpBuilder = grpBuilder.SetName(grpName).SetNamespace(namespaces["x"]).SetPodSelector(map[string]string{"pod": "j"}, nil) + builder := &AntreaNetworkPolicySpecBuilder{} + builder = builder.SetName(namespaces["x"], "anp-deny-grp-with-xj-to-xd-egress"). + SetPriority(1.0). + SetAppliedToGroup([]ANPAppliedToSpec{{Group: grpName}}) + builder.AddEgress(ProtocolTCP, &p80, nil, nil, nil, nil, nil, nil, nil, map[string]string{"pod": "d"}, nil, + nil, nil, nil, crdv1alpha1.RuleActionDrop, "", "") + cp := []*CustomProbe{ + { + SourcePod: CustomPod{ + Pod: NewPod(namespaces["x"], "j"), + Labels: map[string]string{"pod": "j"}, + }, + DestPod: CustomPod{ + Pod: NewPod(namespaces["x"], "d"), + Labels: map[string]string{"pod": "d"}, + }, + ExpectConnectivity: Dropped, + Port: p80, + }, + } + testStep := []*TestStep{ + { + "Port 80", + nil, + []metav1.Object{grpBuilder.Get(), builder.Get()}, + []int32{80}, + ProtocolTCP, + 0, + cp, + }, + } + testCase := []*TestCase{ + {"ANP Drop Egress From Group with Pod: x/j to Pod: x/d for Pod ADD events", testStep}, + } + executeTestsWithData(t, testCase, data) +} + +func testANPGroupServiceRefPodAdd(t *testing.T, data *TestData) { + svc1 := k8sUtils.BuildService("svc1", namespaces["x"], 80, 80, map[string]string{"app": "a"}, nil) + svc2 := k8sUtils.BuildService("svc2", namespaces["x"], 80, 80, map[string]string{"app": "b"}, nil) + + grp1Name, grp2Name := "grp-svc1", "grp-svc2" + grpBuilder1 := &GroupSpecBuilder{} + grpBuilder1 = grpBuilder1.SetName(grp1Name).SetNamespace(namespaces["x"]).SetServiceReference(namespaces["x"], "svc1") + grpBuilder2 := &GroupSpecBuilder{} + grpBuilder2 = grpBuilder2.SetName(grp2Name).SetNamespace(namespaces["x"]).SetServiceReference(namespaces["x"], "svc2") + + builder := &AntreaNetworkPolicySpecBuilder{} + builder = builder.SetName(namespaces["x"], "anp-grp-svc-ref").SetPriority(1.0).SetAppliedToGroup([]ANPAppliedToSpec{{Group: grp1Name}}) + builder.AddIngress(ProtocolTCP, &p80, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, crdv1alpha1.RuleActionDrop, grp2Name, "") + + svc1PodName := randName("test-pod-svc1-") + svc2PodName := randName("test-pod-svc2-") + cp := []*CustomProbe{ + { + SourcePod: CustomPod{ + Pod: NewPod(namespaces["x"], svc2PodName), + Labels: map[string]string{"pod": svc2PodName, "app": "b"}, + }, + DestPod: CustomPod{ + Pod: NewPod(namespaces["x"], svc1PodName), + Labels: map[string]string{"pod": svc1PodName, "app": "a"}, + }, + ExpectConnectivity: Dropped, + Port: p80, + }, + } + + reachability := NewReachability(allPods, Connected) + reachability.Expect(Pod(namespaces["x"]+"/b"), Pod(namespaces["x"]+"/a"), Dropped) + testStep := &TestStep{ + "Port 80 updated", + reachability, + []metav1.Object{svc1, svc2, grpBuilder1.Get(), grpBuilder2.Get(), builder.Get()}, + []int32{80}, + ProtocolTCP, + 0, + cp, + } + + testSteps := []*TestStep{testStep} + testCase := []*TestCase{ + {"ANP Group Service Reference add pod", testSteps}, + } + executeTestsWithData(t, testCase, data) +} + +func testANPGroupServiceRefDelete(t *testing.T) { + svc1 := k8sUtils.BuildService("svc1", namespaces["x"], 80, 80, map[string]string{"app": "a"}, nil) + svc2 := k8sUtils.BuildService("svc2", namespaces["x"], 80, 80, map[string]string{"app": "b"}, nil) + k8sUtils.CreateOrUpdateService(svc1) + failOnError(waitForResourceReady(t, timeout, svc1), t) + k8sUtils.CreateOrUpdateService(svc2) + failOnError(waitForResourceReady(t, timeout, svc2), t) + + grp1Name, grp2Name := "grp-svc1", "grp-svc2" + grpBuilder1 := &GroupSpecBuilder{} + grpBuilder1 = grpBuilder1.SetName(grp1Name).SetNamespace(namespaces["x"]).SetServiceReference(namespaces["x"], "svc1") + grpBuilder2 := &GroupSpecBuilder{} + grpBuilder2 = grpBuilder2.SetName(grp2Name).SetNamespace(namespaces["x"]).SetServiceReference(namespaces["x"], "svc2") + grp1 := grpBuilder1.Get() + k8sUtils.CreateOrUpdateV1Alpha3Group(grp1) + failOnError(waitForResourceReady(t, timeout, grp1), t) + grp2 := grpBuilder2.Get() + k8sUtils.CreateOrUpdateV1Alpha3Group(grp2) + failOnError(waitForResourceReady(t, timeout, grp2), t) + + builder := &AntreaNetworkPolicySpecBuilder{} + builder = builder.SetName(namespaces["x"], "anp-grp-svc-ref").SetPriority(1.0).SetAppliedToGroup([]ANPAppliedToSpec{{Group: grp1Name}}) + builder.AddIngress(ProtocolTCP, &p80, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, crdv1alpha1.RuleActionDrop, grp2Name, "") + anp := builder.Get() + k8sUtils.CreateOrUpdateANP(anp) + failOnError(waitForResourceReady(t, timeout, anp), t) + + reachability := NewReachability(allPods, Connected) + reachability.Expect(Pod(namespaces["x"]+"/b"), Pod(namespaces["x"]+"/a"), Dropped) + k8sUtils.Validate(allPods, reachability, []int32{80}, ProtocolTCP) + _, wrong, _ := reachability.Summary() + if wrong != 0 { + t.Errorf("failure -- %d wrong results", wrong) + reachability.PrintSummary(true, true, true) + } + // Delete services, pods should be connected. + failOnError(k8sUtils.DeleteService(svc1.Namespace, svc1.Name), t) + failOnError(k8sUtils.DeleteService(svc2.Namespace, svc2.Name), t) + time.Sleep(defaultInterval) + reachability2 := NewReachability(allPods, Connected) + k8sUtils.Validate(allPods, reachability2, []int32{80}, ProtocolTCP) + _, wrong, _ = reachability2.Summary() + if wrong != 0 { + t.Errorf("failure -- %d wrong results", wrong) + reachability2.PrintSummary(true, true, true) + } + // Cleanup test resources. + failOnError(k8sUtils.DeleteANP(builder.Namespace, builder.Name), t) +} + +func testANPGroupServiceRefCreateAndUpdate(t *testing.T) { + svc1 := k8sUtils.BuildService("svc1", namespaces["x"], 80, 80, map[string]string{"app": "a"}, nil) + svc2 := k8sUtils.BuildService("svc2", namespaces["x"], 80, 80, map[string]string{"app": "b"}, nil) + + grp1Name, grp2Name := "grp-svc1", "grp-svc2" + grpBuilder1 := &GroupSpecBuilder{} + grpBuilder1 = grpBuilder1.SetName(grp1Name).SetNamespace(namespaces["x"]).SetServiceReference(namespaces["x"], "svc1") + grpBuilder2 := &GroupSpecBuilder{} + grpBuilder2 = grpBuilder2.SetName(grp2Name).SetNamespace(namespaces["x"]).SetServiceReference(namespaces["x"], "svc2") + + builder := &AntreaNetworkPolicySpecBuilder{} + builder = builder.SetName(namespaces["x"], "anp-grp-svc-ref").SetPriority(1.0).SetAppliedToGroup([]ANPAppliedToSpec{{Group: grp1Name}}) + builder.AddIngress(ProtocolTCP, &p80, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, crdv1alpha1.RuleActionDrop, grp2Name, "") + + // Pods backing svc1 (label pod=a) in Namespace x should not allow ingress from Pods backing svc2 (label pod=b) in Namespace x. + reachability := NewReachability(allPods, Connected) + reachability.Expect(Pod(namespaces["x"]+"/b"), Pod(namespaces["x"]+"/a"), Dropped) + testStep1 := &TestStep{ + "Port 80", + reachability, + []metav1.Object{svc1, svc2, grpBuilder1.Get(), grpBuilder2.Get(), builder.Get()}, + []int32{80}, + ProtocolTCP, + 0, + nil, + } + + // Test update selector of Service referred in grp-svc1, and update serviceReference of grp-svc2. + svc1Updated := k8sUtils.BuildService("svc1", namespaces["x"], 80, 80, map[string]string{"app": "b"}, nil) + svc3 := k8sUtils.BuildService("svc3", namespaces["x"], 80, 80, map[string]string{"app": "c"}, nil) + grpBuilder2Updated := grpBuilder2.SetNamespace(namespaces["x"]).SetServiceReference(namespaces["x"], "svc3") + + // Pods backing svc1 (label pod=b) in namespace x should not allow ingress from Pods backing svc3 (label pod=d) in namespace x. + reachability2 := NewReachability(allPods, Connected) + reachability2.Expect(Pod(namespaces["x"]+"/c"), Pod(namespaces["x"]+"/b"), Dropped) + testStep2 := &TestStep{ + "Port 80 updated", + reachability2, + []metav1.Object{svc1Updated, svc3, grpBuilder1.Get(), grpBuilder2Updated.Get()}, + []int32{80}, + ProtocolTCP, + 0, + nil, + } + + testSteps := []*TestStep{testStep1, testStep2} + testCase := []*TestCase{ + {"ANP Group Service Reference create and update", testSteps}, + } + executeTests(t, testCase) +} + +func testANPGroupRefRuleIPBlocks(t *testing.T) { + podXBIP, _ := podIPs[namespaces["x"]+"/b"] + podXCIP, _ := podIPs[namespaces["x"]+"/c"] + // There are three situations of a Pod's IP(s): + // 1. Only one IPv4 address. + // 2. Only one IPv6 address. + // 3. One IPv4 and one IPv6 address, and we don't know the order in list. + // We need to add all IP(s) of Pods as CIDR to IPBlock. + genCIDR := func(ip string) string { + if strings.Contains(ip, ".") { + return ip + "/32" + } + return ip + "/128" + } + var ipBlock []crdv1alpha1.IPBlock + for i := 0; i < len(podXBIP); i++ { + ipBlock = append(ipBlock, crdv1alpha1.IPBlock{CIDR: genCIDR(podXBIP[i])}) + ipBlock = append(ipBlock, crdv1alpha1.IPBlock{CIDR: genCIDR(podXCIP[i])}) + } + + grpName := "grp-ipblocks-pod-xb-xc" + grpBuilder := &GroupSpecBuilder{} + grpBuilder = grpBuilder.SetName(grpName).SetNamespace(namespaces["x"]).SetIPBlocks(ipBlock) + + builder := &AntreaNetworkPolicySpecBuilder{} + builder = builder.SetName(namespaces["x"], "anp-deny-xb-xc-ips-ingress-for-xa"). + SetPriority(1.0). + SetAppliedToGroup([]ANPAppliedToSpec{{PodSelector: map[string]string{"pod": "a"}}}) + builder.AddIngress(ProtocolTCP, &p80, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, crdv1alpha1.RuleActionDrop, grpName, "") + + reachability := NewReachability(allPods, Connected) + reachability.Expect(Pod(namespaces["x"]+"/b"), Pod(namespaces["x"]+"/a"), Dropped) + reachability.Expect(Pod(namespaces["x"]+"/c"), Pod(namespaces["x"]+"/a"), Dropped) + testStep := []*TestStep{ + { + "Port 80", + reachability, + []metav1.Object{builder.Get(), grpBuilder.Get()}, + []int32{80}, + ProtocolTCP, + 0, + nil, + }, + } + testCase := []*TestCase{ + {"ANP Drop Ingress From Group with ipBlocks to Pod: x/a", testStep}, + } + executeTests(t, testCase) +} + +func testANPNestedGroupCreateAndUpdate(t *testing.T, data *TestData) { + svc1 := k8sUtils.BuildService("svc1", namespaces["x"], 80, 80, map[string]string{"app": "a"}, nil) + svc1PodName := randName("test-pod-svc1-") + grp1Name, grp2Name, grp3Name := "grp-svc-x-a", "grp-select-x-b", "grp-select-x-c" + grpBuilder1 := &GroupSpecBuilder{} + grpBuilder1 = grpBuilder1.SetName(grp1Name).SetNamespace(namespaces["x"]).SetServiceReference(namespaces["x"], "svc1") + grpBuilder2 := &GroupSpecBuilder{} + grpBuilder2 = grpBuilder2.SetName(grp2Name).SetNamespace(namespaces["x"]).SetPodSelector(map[string]string{"pod": "b"}, nil) + grpBuilder3 := &GroupSpecBuilder{} + grpBuilder3 = grpBuilder3.SetName(grp3Name).SetNamespace(namespaces["x"]).SetPodSelector(map[string]string{"pod": "c"}, nil) + grpNestedName := "grp-nested" + grpBuilderNested := &GroupSpecBuilder{} + grpBuilderNested = grpBuilderNested.SetName(grpNestedName).SetNamespace(namespaces["x"]).SetChildGroups([]string{grp1Name, grp3Name}) + + builder := &AntreaNetworkPolicySpecBuilder{} + builder = builder.SetName(namespaces["x"], "anp-nested-grp").SetPriority(1.0). + SetAppliedToGroup([]ANPAppliedToSpec{{}}). + AddIngress(ProtocolTCP, &p80, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, crdv1alpha1.RuleActionDrop, grpNestedName, "") + + // Pods in Namespace x should not allow traffic from Pods backing svc1 (label pod=a) in Namespace x. + // Note that in this testStep grp3 will not be created yet, so even though grp-nested selects grp1 and + // grp3 as childGroups, only members of grp1 will be included as this time. + reachability := NewReachability(allPods, Connected) + reachability.ExpectEgressToNamespace(Pod(namespaces["x"]+"/a"), namespaces["x"], Dropped) + reachability.ExpectSelf(allPods, Connected) + + testStep1 := &TestStep{ + "Port 80", + reachability, + // Note in this testcase the Group is created after the ANP + []metav1.Object{builder.Get(), svc1, grpBuilder1.Get(), grpBuilderNested.Get()}, + []int32{80}, + ProtocolTCP, + 0, + nil, + } + + // Test update "grp-nested" to include "grp-select-x-b" as well. + grpBuilderNested = grpBuilderNested.SetChildGroups([]string{grp1Name, grp2Name, grp3Name}) + // In addition to x/a, all traffic from x/b to Namespace x should also be denied. + reachability2 := NewReachability(allPods, Connected) + reachability2.ExpectEgressToNamespace(Pod(namespaces["x"]+"/a"), namespaces["x"], Dropped) + reachability2.ExpectEgressToNamespace(Pod(namespaces["x"]+"/b"), namespaces["x"], Dropped) + reachability2.ExpectSelf(allPods, Connected) + // New member in grp-svc-x-a should be reflected in grp-nested as well. + cp := []*CustomProbe{ + { + SourcePod: CustomPod{ + Pod: NewPod(namespaces["x"], svc1PodName), + Labels: map[string]string{"pod": svc1PodName, "app": "a"}, + }, + DestPod: CustomPod{ + Pod: NewPod(namespaces["x"], "test-add-pod-ns-x"), + Labels: map[string]string{"pod": "test-add-pod-ns-x"}, + }, + ExpectConnectivity: Dropped, + Port: p80, + }, + } + testStep2 := &TestStep{ + "Port 80 updated", + reachability2, + []metav1.Object{grpBuilder2.Get(), grpBuilderNested.Get()}, + []int32{80}, + ProtocolTCP, + 0, + cp, + } + + // In this testStep grp3 is created. It's members should reflect in grp-nested + // and as a result, all traffic from x/c to Namespace x should be denied as well. + reachability3 := NewReachability(allPods, Connected) + reachability3.ExpectEgressToNamespace(Pod(namespaces["x"]+"/a"), namespaces["x"], Dropped) + reachability3.ExpectEgressToNamespace(Pod(namespaces["x"]+"/b"), namespaces["x"], Dropped) + reachability3.ExpectEgressToNamespace(Pod(namespaces["x"]+"/c"), namespaces["x"], Dropped) + reachability3.ExpectSelf(allPods, Connected) + testStep3 := &TestStep{ + "Port 80 updated", + reachability3, + []metav1.Object{grpBuilder3.Get()}, + []int32{80}, + ProtocolTCP, + 0, + nil, + } + + testSteps := []*TestStep{testStep1, testStep2, testStep3} + testCase := []*TestCase{ + {"ANP nested Group create and update", testSteps}, + } + executeTestsWithData(t, testCase, data) +} + // testBaselineNamespaceIsolation tests that an ACNP in the baseline Tier is able to enforce default namespace isolation, // which can be later overridden by developer K8s NetworkPolicies. func testBaselineNamespaceIsolation(t *testing.T) { @@ -1808,8 +2416,8 @@ func testANPPortRange(t *testing.T) { builder = builder.SetName(namespaces["y"], "anp-deny-yb-to-xc-egress-port-range"). SetPriority(1.0). SetAppliedToGroup([]ANPAppliedToSpec{{PodSelector: map[string]string{"pod": "b"}}}) - builder.AddEgress(ProtocolTCP, &p8080, nil, &p8082, nil, nil, nil, nil, nil, map[string]string{"pod": "c"}, map[string]string{"ns": namespaces["x"]}, - nil, nil, nil, crdv1alpha1.RuleActionDrop, "anp-port-range") + builder.AddEgress(ProtocolTCP, &p8080, nil, &p8085, nil, nil, nil, nil, nil, map[string]string{"pod": "c"}, map[string]string{"ns": namespaces["x"]}, + nil, nil, nil, crdv1alpha1.RuleActionDrop, "", "anp-port-range") reachability := NewReachability(allPods, Connected) reachability.Expect(Pod(namespaces["y"]+"/b"), Pod(namespaces["x"]+"/c"), Dropped) @@ -1839,7 +2447,7 @@ func testANPBasic(t *testing.T) { SetPriority(1.0). SetAppliedToGroup([]ANPAppliedToSpec{{PodSelector: map[string]string{"pod": "a"}}}) builder.AddIngress(ProtocolTCP, &p80, nil, nil, nil, nil, nil, nil, nil, map[string]string{"pod": "b"}, map[string]string{"ns": namespaces["x"]}, - nil, nil, nil, crdv1alpha1.RuleActionDrop, "") + nil, nil, nil, crdv1alpha1.RuleActionDrop, "", "") reachability := NewReachability(allPods, Connected) reachability.Expect(Pod(namespaces["x"]+"/b"), Pod(namespaces["y"]+"/a"), Dropped) @@ -1890,12 +2498,12 @@ func testANPMultipleAppliedTo(t *testing.T, data *TestData, singleRule bool) { if singleRule { builder.SetAppliedToGroup([]ANPAppliedToSpec{{PodSelector: map[string]string{"pod": "a"}}, {PodSelector: map[string]string{tempLabel: ""}}}) builder.AddIngress(ProtocolTCP, &p80, nil, nil, nil, nil, nil, nil, nil, map[string]string{"pod": "b"}, map[string]string{"ns": namespaces["x"]}, - nil, nil, nil, crdv1alpha1.RuleActionDrop, "") + nil, nil, nil, crdv1alpha1.RuleActionDrop, "", "") } else { builder.AddIngress(ProtocolTCP, &p80, nil, nil, nil, nil, nil, nil, nil, map[string]string{"pod": "b"}, map[string]string{"ns": namespaces["x"]}, - nil, nil, []ANPAppliedToSpec{{PodSelector: map[string]string{"pod": "a"}}}, crdv1alpha1.RuleActionDrop, "") + nil, nil, []ANPAppliedToSpec{{PodSelector: map[string]string{"pod": "a"}}}, crdv1alpha1.RuleActionDrop, "", "") builder.AddIngress(ProtocolTCP, &p80, nil, nil, nil, nil, nil, nil, nil, map[string]string{"pod": "b"}, map[string]string{"ns": namespaces["x"]}, - nil, nil, []ANPAppliedToSpec{{PodSelector: map[string]string{tempLabel: ""}}}, crdv1alpha1.RuleActionDrop, "") + nil, nil, []ANPAppliedToSpec{{PodSelector: map[string]string{tempLabel: ""}}}, crdv1alpha1.RuleActionDrop, "", "") } reachability := NewReachability(allPods, Connected) @@ -2134,9 +2742,9 @@ func testAppliedToPerRule(t *testing.T) { anpATGrp1 := ANPAppliedToSpec{PodSelector: map[string]string{"pod": "a"}, PodSelectorMatchExp: nil} anpATGrp2 := ANPAppliedToSpec{PodSelector: map[string]string{"pod": "b"}, PodSelectorMatchExp: nil} builder.AddIngress(ProtocolTCP, &p80, nil, nil, nil, nil, nil, nil, nil, map[string]string{"pod": "b"}, map[string]string{"ns": namespaces["x"]}, - nil, nil, []ANPAppliedToSpec{anpATGrp1}, crdv1alpha1.RuleActionDrop, "") + nil, nil, []ANPAppliedToSpec{anpATGrp1}, crdv1alpha1.RuleActionDrop, "", "") builder.AddIngress(ProtocolTCP, &p80, nil, nil, nil, nil, nil, nil, nil, map[string]string{"pod": "b"}, map[string]string{"ns": namespaces["z"]}, - nil, nil, []ANPAppliedToSpec{anpATGrp2}, crdv1alpha1.RuleActionDrop, "") + nil, nil, []ANPAppliedToSpec{anpATGrp2}, crdv1alpha1.RuleActionDrop, "", "") reachability := NewReachability(allPods, Connected) reachability.Expect(Pod(namespaces["x"]+"/b"), Pod(namespaces["y"]+"/a"), Dropped) @@ -3421,6 +4029,9 @@ func applyTestStepResources(t *testing.T, step *TestStep) { case *crdv1alpha2.ClusterGroup: _, err := k8sUtils.CreateOrUpdateV1Alpha2CG(o) failOnError(err, t) + case *crdv1alpha3.Group: + _, err := k8sUtils.CreateOrUpdateV1Alpha3Group(o) + failOnError(err, t) case *v1.Service: _, err := k8sUtils.CreateOrUpdateService(o) failOnError(err, t) @@ -3434,7 +4045,7 @@ func cleanupTestCaseResources(t *testing.T, c *TestCase) { // TestSteps in a TestCase may first create and then update the same resource. // Use sets to avoid duplicates. acnpsToDelete, anpsToDelete, npsToDelete := sets.String{}, sets.String{}, sets.String{} - svcsToDelete, v1a2GroupsToDelete, v1a3GroupsToDelete := sets.String{}, sets.String{}, sets.String{} + svcsToDelete, v1a2ClusterGroupsToDelete, v1a3ClusterGroupsToDelete, v1a3GroupsToDelete := sets.String{}, sets.String{}, sets.String{}, sets.String{} for _, step := range c.Steps { for _, r := range step.TestResources { switch o := r.(type) { @@ -3445,9 +4056,11 @@ func cleanupTestCaseResources(t *testing.T, c *TestCase) { case *v1net.NetworkPolicy: npsToDelete.Insert(o.Namespace + "/" + o.Name) case *crdv1alpha3.ClusterGroup: - v1a3GroupsToDelete.Insert(o.Name) + v1a3ClusterGroupsToDelete.Insert(o.Name) case *crdv1alpha2.ClusterGroup: - v1a2GroupsToDelete.Insert(o.Name) + v1a2ClusterGroupsToDelete.Insert(o.Name) + case *crdv1alpha3.Group: + v1a3GroupsToDelete.Insert(o.Namespace + "/" + o.Name) case *v1.Service: svcsToDelete.Insert(o.Namespace + "/" + o.Name) } @@ -3466,12 +4079,17 @@ func cleanupTestCaseResources(t *testing.T, c *TestCase) { name := strings.Split(np, "/")[1] failOnError(k8sUtils.DeleteNetworkPolicy(namespace, name), t) } - for cg := range v1a2GroupsToDelete { + for cg := range v1a2ClusterGroupsToDelete { failOnError(k8sUtils.DeleteV1Alpha2CG(cg), t) } - for cg := range v1a3GroupsToDelete { + for cg := range v1a3ClusterGroupsToDelete { failOnError(k8sUtils.DeleteV1Alpha3CG(cg), t) } + for grp := range v1a3GroupsToDelete { + namespace := strings.Split(grp, "/")[0] + name := strings.Split(grp, "/")[1] + failOnError(k8sUtils.DeleteV1Alpha3Group(namespace, name), t) + } for svc := range svcsToDelete { namespace := strings.Split(svc, "/")[0] name := strings.Split(svc, "/")[1] @@ -3526,6 +4144,7 @@ func waitForResourceReady(t *testing.T, timeout time.Duration, obj metav1.Object case *crdv1alpha1.Tier: case *crdv1alpha2.ClusterGroup: case *crdv1alpha3.ClusterGroup: + case *crdv1alpha3.Group: } return nil } @@ -3567,6 +4186,8 @@ func TestAntreaPolicy(t *testing.T) { t.Run("Case=ANPTierDoesNotExistDenied", func(t *testing.T) { testInvalidANPTierDoesNotExist(t) }) t.Run("Case=ANPPortRangePortUnsetDenied", func(t *testing.T) { testInvalidANPPortRangePortUnset(t) }) t.Run("Case=ANPPortRangePortEndPortSmallDenied", func(t *testing.T) { testInvalidANPPortRangeEndPortSmall(t) }) + t.Run("Case=ANPIngressPeerGroupSetWithIPBlock", func(t *testing.T) { testInvalidANPIngressPeerGroupSetWithIPBlock(t) }) + t.Run("Case=ANPIngressPeerGroupSetWithPodSelector", func(t *testing.T) { testInvalidANPIngressPeerGroupSetWithPodSelector(t) }) t.Run("Case=ACNPInvalidPodSelectorNsSelectorMatchExpressions", func(t *testing.T) { testInvalidACNPPodSelectorNsSelectorMatchExpressions(t, data) }) }) @@ -3636,6 +4257,18 @@ func TestAntreaPolicy(t *testing.T) { t.Run("Case=ACNPClusterGroupServiceRef", func(t *testing.T) { testACNPClusterGroupServiceRefCreateAndUpdate(t, data) }) t.Run("Case=ACNPNestedClusterGroup", func(t *testing.T) { testACNPNestedClusterGroupCreateAndUpdate(t, data) }) t.Run("Case=ACNPNestedIPBlockClusterGroup", func(t *testing.T) { testACNPNestedIPBlockClusterGroupCreateAndUpdate(t) }) + t.Run("Case=ANPGroupEgressRulePodsAToGrpWithPodsC", func(t *testing.T) { testANPEgressRulePodsAToGrpWithPodsC(t) }) + t.Run("Case=ANPIngressRuleDenyGrpWithXCtoXA", func(t *testing.T) { testANPIngressRuleDenyGrpWithXCtoXA(t) }) + t.Run("Case=ANPGroupUpdate", func(t *testing.T) { testANPGroupUpdate(t) }) + t.Run("Case=ANPGroupAppliedToDenyXBToGrpWithXA", func(t *testing.T) { testANPAppliedToDenyXBtoGrpWithXA(t) }) + t.Run("Case=ANPGroupAppliedToRuleGrpWithPodsAToPodsC", func(t *testing.T) { testANPAppliedToRuleGrpWithPodsAToPodsC(t) }) + t.Run("Case=ANPGroupUpdateAppliedTo", func(t *testing.T) { testANPGroupUpdateAppliedTo(t) }) + t.Run("Case=ANPGroupAppliedToPodAdd", func(t *testing.T) { testANPGroupAppliedToPodAdd(t, data) }) + t.Run("Case=ANPGroupServiceRefPodAdd", func(t *testing.T) { testANPGroupServiceRefPodAdd(t, data) }) + t.Run("Case=ANPGroupServiceRefDelete", func(t *testing.T) { testANPGroupServiceRefDelete(t) }) + t.Run("Case=ANPGroupServiceRef", func(t *testing.T) { testANPGroupServiceRefCreateAndUpdate(t) }) + t.Run("Case=ANPGroupRefRuleIPBlocks", func(t *testing.T) { testANPGroupRefRuleIPBlocks(t) }) + t.Run("Case=ANPNestedGroup", func(t *testing.T) { testANPNestedGroupCreateAndUpdate(t, data) }) t.Run("Case=ACNPFQDNPolicy", func(t *testing.T) { testFQDNPolicy(t) }) t.Run("Case=FQDNPolicyInCluster", func(t *testing.T) { testFQDNPolicyInClusterService(t) }) t.Run("Case=ACNPToServices", func(t *testing.T) { testToServices(t) }) @@ -3683,7 +4316,7 @@ func TestAntreaPolicyStatus(t *testing.T) { SetPriority(1.0). SetAppliedToGroup([]ANPAppliedToSpec{{PodSelector: map[string]string{"app": "nginx"}}}) anpBuilder.AddIngress(ProtocolTCP, &p80, nil, nil, nil, nil, nil, nil, nil, map[string]string{"pod": "b"}, map[string]string{"ns": namespaces["x"]}, - nil, nil, nil, crdv1alpha1.RuleActionAllow, "") + nil, nil, nil, crdv1alpha1.RuleActionAllow, "", "") anp := anpBuilder.Get() log.Debugf("creating ANP %v", anp.Name) _, err = data.crdClient.CrdV1alpha1().NetworkPolicies(anp.Namespace).Create(context.TODO(), anp, metav1.CreateOptions{}) @@ -3707,6 +4340,7 @@ func TestAntreaPolicyStatus(t *testing.T) { ObservedGeneration: 1, CurrentNodesRealized: 2, DesiredNodesRealized: 2, + Conditions: networkpolicy.GenerateNetworkPolicyCondition(nil), } checkANPStatus(t, data, anp, expectedStatus) checkACNPStatus(t, data, acnp, expectedStatus) @@ -3731,9 +4365,9 @@ func TestAntreaPolicyStatusWithAppliedToPerRule(t *testing.T) { anpBuilder = anpBuilder.SetName(data.testNamespace, "anp-applied-to-per-rule"). SetPriority(1.0) anpBuilder.AddIngress(ProtocolTCP, &p80, nil, nil, nil, nil, nil, nil, nil, map[string]string{"pod": "b"}, map[string]string{"ns": namespaces["x"]}, - nil, nil, []ANPAppliedToSpec{{PodSelector: map[string]string{"antrea-e2e": server0Name}}}, crdv1alpha1.RuleActionAllow, "") + nil, nil, []ANPAppliedToSpec{{PodSelector: map[string]string{"antrea-e2e": server0Name}}}, crdv1alpha1.RuleActionAllow, "", "") anpBuilder.AddIngress(ProtocolTCP, &p80, nil, nil, nil, nil, nil, nil, nil, map[string]string{"pod": "b"}, map[string]string{"ns": namespaces["x"]}, - nil, nil, []ANPAppliedToSpec{{PodSelector: map[string]string{"antrea-e2e": server1Name}}}, crdv1alpha1.RuleActionAllow, "") + nil, nil, []ANPAppliedToSpec{{PodSelector: map[string]string{"antrea-e2e": server1Name}}}, crdv1alpha1.RuleActionAllow, "", "") anp := anpBuilder.Get() log.Debugf("creating ANP %v", anp.Name) anp, err = data.crdClient.CrdV1alpha1().NetworkPolicies(anp.Namespace).Create(context.TODO(), anp, metav1.CreateOptions{}) @@ -3745,6 +4379,7 @@ func TestAntreaPolicyStatusWithAppliedToPerRule(t *testing.T) { ObservedGeneration: 1, CurrentNodesRealized: 2, DesiredNodesRealized: 2, + Conditions: networkpolicy.GenerateNetworkPolicyCondition(nil), }) // Remove the second ingress rule. @@ -3756,6 +4391,7 @@ func TestAntreaPolicyStatusWithAppliedToPerRule(t *testing.T) { ObservedGeneration: 2, CurrentNodesRealized: 1, DesiredNodesRealized: 1, + Conditions: networkpolicy.GenerateNetworkPolicyCondition(nil), }) } @@ -3766,7 +4402,7 @@ func checkANPStatus(t *testing.T, data *TestData, anp *crdv1alpha1.NetworkPolicy if err != nil { return false, err } - return anp.Status == expectedStatus, nil + return networkpolicy.NetworkPolicyStatusEqual(anp.Status, expectedStatus), nil }) assert.NoError(t, err, "Antrea NetworkPolicy failed to reach expected status") return anp @@ -3779,7 +4415,7 @@ func checkACNPStatus(t *testing.T, data *TestData, acnp *crdv1alpha1.ClusterNetw if err != nil { return false, err } - return acnp.Status == expectedStatus, nil + return networkpolicy.NetworkPolicyStatusEqual(acnp.Status, expectedStatus), nil }) assert.NoError(t, err, "Antrea ClusterNetworkPolicy failed to reach expected status") return acnp diff --git a/test/e2e/flowaggregator_test.go b/test/e2e/flowaggregator_test.go index 8809a352533..3a65970d024 100644 --- a/test/e2e/flowaggregator_test.go +++ b/test/e2e/flowaggregator_test.go @@ -1160,7 +1160,7 @@ func deployAntreaNetworkPolicies(t *testing.T, data *TestData, srcPod, dstPod st SetPriority(2.0). SetAppliedToGroup([]utils.ANPAppliedToSpec{{PodSelector: map[string]string{"antrea-e2e": dstPod}}}) builder1 = builder1.AddIngress(utils.ProtocolTCP, nil, nil, nil, nil, nil, nil, nil, nil, map[string]string{"antrea-e2e": srcPod}, map[string]string{}, - nil, nil, nil, secv1alpha1.RuleActionAllow, testIngressRuleName) + nil, nil, nil, secv1alpha1.RuleActionAllow, "", testIngressRuleName) anp1 = builder1.Get() anp1, err1 := k8sUtils.CreateOrUpdateANP(anp1) if err1 != nil { @@ -1173,7 +1173,7 @@ func deployAntreaNetworkPolicies(t *testing.T, data *TestData, srcPod, dstPod st SetPriority(2.0). SetAppliedToGroup([]utils.ANPAppliedToSpec{{PodSelector: map[string]string{"antrea-e2e": srcPod}}}) builder2 = builder2.AddEgress(utils.ProtocolTCP, nil, nil, nil, nil, nil, nil, nil, nil, map[string]string{"antrea-e2e": dstPod}, map[string]string{}, - nil, nil, nil, secv1alpha1.RuleActionAllow, testEgressRuleName) + nil, nil, nil, secv1alpha1.RuleActionAllow, "", testEgressRuleName) anp2 = builder2.Get() anp2, err2 := k8sUtils.CreateOrUpdateANP(anp2) if err2 != nil { @@ -1198,24 +1198,24 @@ func deployDenyAntreaNetworkPolicies(t *testing.T, data *TestData, srcPod, podRe SetPriority(2.0). SetAppliedToGroup([]utils.ANPAppliedToSpec{{PodSelector: map[string]string{"antrea-e2e": podReject}}}) builder1 = builder1.AddIngress(utils.ProtocolTCP, nil, nil, nil, nil, nil, nil, nil, nil, map[string]string{"antrea-e2e": srcPod}, map[string]string{}, - nil, nil, nil, secv1alpha1.RuleActionReject, testIngressRuleName) + nil, nil, nil, secv1alpha1.RuleActionReject, "", testIngressRuleName) builder2 = builder2.SetName(data.testNamespace, ingressDropANPName). SetPriority(2.0). SetAppliedToGroup([]utils.ANPAppliedToSpec{{PodSelector: map[string]string{"antrea-e2e": podDrop}}}) builder2 = builder2.AddIngress(utils.ProtocolTCP, nil, nil, nil, nil, nil, nil, nil, nil, map[string]string{"antrea-e2e": srcPod}, map[string]string{}, - nil, nil, nil, secv1alpha1.RuleActionDrop, testIngressRuleName) + nil, nil, nil, secv1alpha1.RuleActionDrop, "", testIngressRuleName) } else { // apply reject and drop egress rule to source pod builder1 = builder1.SetName(data.testNamespace, egressRejectANPName). SetPriority(2.0). SetAppliedToGroup([]utils.ANPAppliedToSpec{{PodSelector: map[string]string{"antrea-e2e": srcPod}}}) builder1 = builder1.AddEgress(utils.ProtocolTCP, nil, nil, nil, nil, nil, nil, nil, nil, map[string]string{"antrea-e2e": podReject}, map[string]string{}, - nil, nil, nil, secv1alpha1.RuleActionReject, testEgressRuleName) + nil, nil, nil, secv1alpha1.RuleActionReject, "", testEgressRuleName) builder2 = builder2.SetName(data.testNamespace, egressDropANPName). SetPriority(2.0). SetAppliedToGroup([]utils.ANPAppliedToSpec{{PodSelector: map[string]string{"antrea-e2e": srcPod}}}) builder2 = builder2.AddEgress(utils.ProtocolTCP, nil, nil, nil, nil, nil, nil, nil, nil, map[string]string{"antrea-e2e": podDrop}, map[string]string{}, - nil, nil, nil, secv1alpha1.RuleActionDrop, testEgressRuleName) + nil, nil, nil, secv1alpha1.RuleActionDrop, "", testEgressRuleName) } anp1 = builder1.Get() anp1, err = k8sUtils.CreateOrUpdateANP(anp1) diff --git a/test/e2e/group_test.go b/test/e2e/group_test.go new file mode 100644 index 00000000000..f92cf903bc9 --- /dev/null +++ b/test/e2e/group_test.go @@ -0,0 +1,284 @@ +// Copyright 2021 Antrea Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package e2e + +import ( + "fmt" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + crdv1alpha1 "antrea.io/antrea/pkg/apis/crd/v1alpha1" + crdv1alpha3 "antrea.io/antrea/pkg/apis/crd/v1alpha3" +) + +func testInvalidGroupIPBlockWithPodSelector(t *testing.T) { + invalidErr := fmt.Errorf("group created with ipblock and podSelector") + gName := "ipb-pod" + pSel := &metav1.LabelSelector{MatchLabels: map[string]string{"pod": "x"}} + cidr := "10.0.0.10/32" + ipb := []crdv1alpha1.IPBlock{{CIDR: cidr}} + g := &crdv1alpha3.Group{ + ObjectMeta: metav1.ObjectMeta{ + Name: gName, + Namespace: namespaces["x"], + }, + Spec: crdv1alpha3.GroupSpec{ + PodSelector: pSel, + IPBlocks: ipb, + }, + } + if _, err := k8sUtils.CreateOrUpdateV1Alpha3Group(g); err == nil { + // Above creation of Group must fail as it is an invalid spec. + failOnError(invalidErr, t) + } +} + +func testInvalidGroupIPBlockWithNSSelector(t *testing.T) { + invalidErr := fmt.Errorf("group created with ipblock and namespaceSelector") + gName := "ipb-ns" + nSel := &metav1.LabelSelector{MatchLabels: map[string]string{"ns": namespaces["y"]}} + cidr := "10.0.0.10/32" + ipb := []crdv1alpha1.IPBlock{{CIDR: cidr}} + g := &crdv1alpha3.Group{ + ObjectMeta: metav1.ObjectMeta{ + Name: gName, + Namespace: namespaces["x"], + }, + Spec: crdv1alpha3.GroupSpec{ + NamespaceSelector: nSel, + IPBlocks: ipb, + }, + } + if _, err := k8sUtils.CreateOrUpdateV1Alpha3Group(g); err == nil { + // Above creation of Group must fail as it is an invalid spec. + failOnError(invalidErr, t) + } +} + +func testInvalidGroupServiceRefWithPodSelector(t *testing.T) { + invalidErr := fmt.Errorf("group created with serviceReference and podSelector") + gName := "svcref-pod-selector" + pSel := &metav1.LabelSelector{MatchLabels: map[string]string{"pod": "x"}} + svcRef := &crdv1alpha1.NamespacedName{ + Namespace: namespaces["y"], + Name: "test-svc", + } + g := &crdv1alpha3.Group{ + ObjectMeta: metav1.ObjectMeta{ + Name: gName, + Namespace: namespaces["y"], + }, + Spec: crdv1alpha3.GroupSpec{ + PodSelector: pSel, + ServiceReference: svcRef, + }, + } + if _, err := k8sUtils.CreateOrUpdateV1Alpha3Group(g); err == nil { + // Above creation of Group must fail as it is an invalid spec. + failOnError(invalidErr, t) + } +} + +func testInvalidGroupServiceRefWithNSSelector(t *testing.T) { + invalidErr := fmt.Errorf("group created with serviceReference and namespaceSelector") + gName := "svcref-ns-selector" + nSel := &metav1.LabelSelector{MatchLabels: map[string]string{"ns": namespaces["y"]}} + svcRef := &crdv1alpha1.NamespacedName{ + Namespace: namespaces["y"], + Name: "test-svc", + } + g := &crdv1alpha3.Group{ + ObjectMeta: metav1.ObjectMeta{ + Name: gName, + Namespace: namespaces["y"], + }, + Spec: crdv1alpha3.GroupSpec{ + NamespaceSelector: nSel, + ServiceReference: svcRef, + }, + } + if _, err := k8sUtils.CreateOrUpdateV1Alpha3Group(g); err == nil { + // Above creation of Group must fail as it is an invalid spec. + failOnError(invalidErr, t) + } +} + +func testInvalidGroupServiceRefWithIPBlock(t *testing.T) { + invalidErr := fmt.Errorf("group created with ipblock and namespaceSelector") + gName := "ipb-svcref" + cidr := "10.0.0.10/32" + ipb := []crdv1alpha1.IPBlock{{CIDR: cidr}} + svcRef := &crdv1alpha1.NamespacedName{ + Namespace: namespaces["y"], + Name: "test-svc", + } + g := &crdv1alpha3.Group{ + ObjectMeta: metav1.ObjectMeta{ + Name: gName, + Namespace: namespaces["y"], + }, + Spec: crdv1alpha3.GroupSpec{ + ServiceReference: svcRef, + IPBlocks: ipb, + }, + } + if _, err := k8sUtils.CreateOrUpdateV1Alpha3Group(g); err == nil { + // Above creation of Group must fail as it is an invalid spec. + failOnError(invalidErr, t) + } +} + +var ( + testChildGroupName = "test-child-grp" + testChildGroupNamespace = "x" +) + +func createChildGroupForTest(t *testing.T) { + g := &crdv1alpha3.Group{ + ObjectMeta: metav1.ObjectMeta{ + Name: testChildGroupName, + Namespace: namespaces[testChildGroupNamespace], + }, + Spec: crdv1alpha3.GroupSpec{ + PodSelector: &metav1.LabelSelector{}, + }, + } + if _, err := k8sUtils.CreateOrUpdateV1Alpha3Group(g); err != nil { + failOnError(err, t) + } +} + +func cleanupChildGroupForTest(t *testing.T) { + if err := k8sUtils.DeleteV1Alpha3Group(namespaces[testChildGroupNamespace], testChildGroupName); err != nil { + failOnError(err, t) + } +} + +func testInvalidGroupChildGroupWithPodSelector(t *testing.T) { + invalidErr := fmt.Errorf("group created with childGroups and podSelector") + gName := "child-group-pod-selector" + pSel := &metav1.LabelSelector{MatchLabels: map[string]string{"pod": "x"}} + g := &crdv1alpha3.Group{ + ObjectMeta: metav1.ObjectMeta{ + Name: gName, + Namespace: namespaces[testChildGroupNamespace], + }, + Spec: crdv1alpha3.GroupSpec{ + PodSelector: pSel, + ChildGroups: []crdv1alpha3.ClusterGroupReference{crdv1alpha3.ClusterGroupReference(testChildGroupName)}, + }, + } + if _, err := k8sUtils.CreateOrUpdateV1Alpha3Group(g); err == nil { + // Above creation of Group must fail as it is an invalid spec. + failOnError(invalidErr, t) + } +} + +func testInvalidGroupChildGroupWithServiceReference(t *testing.T) { + invalidErr := fmt.Errorf("group created with childGroups and ServiceReference") + gName := "child-group-svcref" + svcRef := &crdv1alpha1.NamespacedName{ + Name: "test-svc", + Namespace: namespaces[testChildGroupNamespace], + } + g := &crdv1alpha3.Group{ + ObjectMeta: metav1.ObjectMeta{ + Name: gName, + Namespace: namespaces[testChildGroupNamespace], + }, + Spec: crdv1alpha3.GroupSpec{ + ServiceReference: svcRef, + ChildGroups: []crdv1alpha3.ClusterGroupReference{crdv1alpha3.ClusterGroupReference(testChildGroupName)}, + }, + } + if _, err := k8sUtils.CreateOrUpdateV1Alpha3Group(g); err == nil { + // Above creation of Group must fail as it is an invalid spec. + failOnError(invalidErr, t) + } +} + +func testInvalidGroupMaxNestedLevel(t *testing.T) { + invalidErr := fmt.Errorf("group created with childGroup which has childGroups itself") + gName1, gName2 := "g-nested-1", "g-nested-2" + g1 := &crdv1alpha3.Group{ + ObjectMeta: metav1.ObjectMeta{Namespace: namespaces[testChildGroupNamespace], Name: gName1}, + Spec: crdv1alpha3.GroupSpec{ + ChildGroups: []crdv1alpha3.ClusterGroupReference{crdv1alpha3.ClusterGroupReference(testChildGroupName)}, + }, + } + g2 := &crdv1alpha3.Group{ + ObjectMeta: metav1.ObjectMeta{Namespace: namespaces[testChildGroupNamespace], Name: gName2}, + Spec: crdv1alpha3.GroupSpec{ + ChildGroups: []crdv1alpha3.ClusterGroupReference{crdv1alpha3.ClusterGroupReference(gName1)}, + }, + } + // Try to create g-nested-1 first and then g-nested-2. + // The creation of g-nested-2 should fail as it breaks the max nested level + if _, err := k8sUtils.CreateOrUpdateV1Alpha3Group(g1); err != nil { + // Above creation of Group must succeed as it is a valid spec. + failOnError(err, t) + } + if _, err := k8sUtils.CreateOrUpdateV1Alpha3Group(g2); err == nil { + // Above creation of Group must fail as g-nested-2 cannot have g-nested-1 as childGroup. + failOnError(invalidErr, t) + } + // cleanup g-nested-1 + if err := k8sUtils.DeleteV1Alpha3Group(namespaces[testChildGroupNamespace], gName1); err != nil { + failOnError(err, t) + } + // Try to create g-nested-2 first and then g-nested-1. + // The creation of g-nested-1 should fail as it breaks the max nested level + if _, err := k8sUtils.CreateOrUpdateV1Alpha3Group(g2); err != nil { + // Above creation of Group must succeed as it is a valid spec. + failOnError(err, t) + } + if _, err := k8sUtils.CreateOrUpdateV1Alpha3Group(g1); err == nil { + // Above creation of Group must fail as g-nested-2 cannot have g-nested-1 as childGroup. + failOnError(invalidErr, t) + } + // cleanup g-nested-2 + if err := k8sUtils.DeleteV1Alpha3Group(namespaces[testChildGroupNamespace], gName2); err != nil { + failOnError(err, t) + } +} + +func TestGroup(t *testing.T) { + skipIfHasWindowsNodes(t) + skipIfAntreaPolicyDisabled(t) + + data, err := setupTest(t) + if err != nil { + t.Fatalf("Error when setting up test: %v", err) + } + defer teardownTest(t, data) + initialize(t, data) + + t.Run("TestGroupNamespacedGroupValidate", func(t *testing.T) { + t.Run("Case=IPBlockWithPodSelectorDenied", func(t *testing.T) { testInvalidGroupIPBlockWithPodSelector(t) }) + t.Run("Case=IPBlockWithNamespaceSelectorDenied", func(t *testing.T) { testInvalidGroupIPBlockWithNSSelector(t) }) + t.Run("Case=ServiceRefWithPodSelectorDenied", func(t *testing.T) { testInvalidGroupServiceRefWithPodSelector(t) }) + t.Run("Case=ServiceRefWithNamespaceSelectorDenied", func(t *testing.T) { testInvalidGroupServiceRefWithNSSelector(t) }) + t.Run("Case=ServiceRefWithIPBlockDenied", func(t *testing.T) { testInvalidGroupServiceRefWithIPBlock(t) }) + }) + t.Run("TestGroupNamespacedGroupValidateChildGroup", func(t *testing.T) { + createChildGroupForTest(t) + t.Run("Case=ChildGroupWithPodSelectorDenied", func(t *testing.T) { testInvalidGroupChildGroupWithPodSelector(t) }) + t.Run("Case=ChildGroupWithPodServiceReferenceDenied", func(t *testing.T) { testInvalidGroupChildGroupWithServiceReference(t) }) + t.Run("Case=ChildGroupExceedMaxNestedLevel", func(t *testing.T) { testInvalidGroupMaxNestedLevel(t) }) + cleanupChildGroupForTest(t) + }) + k8sUtils.Cleanup(namespaces) // clean up all cluster-scope resources, including CGs +} diff --git a/test/e2e/k8s_util.go b/test/e2e/k8s_util.go index 85a91118a73..edfb4ad32bf 100644 --- a/test/e2e/k8s_util.go +++ b/test/e2e/k8s_util.go @@ -688,6 +688,26 @@ func (data *TestData) GetV1Alpha2CG(cgName string) (*crdv1alpha2.ClusterGroup, e return data.crdClient.CrdV1alpha2().ClusterGroups().Get(context.TODO(), cgName, metav1.GetOptions{}) } +// CreateOrUpdateV1Alpha3Group is a convenience function for idempotent setup of crd/v1alpha3 Groups +func (k *KubernetesUtils) CreateOrUpdateV1Alpha3Group(g *crdv1alpha3.Group) (*crdv1alpha3.Group, error) { + log.Infof("Creating/updating Group %s/%s", g.Namespace, g.Name) + gReturned, err := k.crdClient.CrdV1alpha3().Groups(g.Namespace).Get(context.TODO(), g.Name, metav1.GetOptions{}) + if err != nil { + gr, err := k.crdClient.CrdV1alpha3().Groups(g.Namespace).Create(context.TODO(), g, metav1.CreateOptions{}) + if err != nil { + log.Infof("Unable to create group %s/%s: %v", g.Namespace, g.Name, err) + return nil, err + } + return gr, nil + } else if gReturned.Name != "" { + log.Debugf("Group %s/%s already exists, updating", g.Namespace, g.Name) + gReturned.Spec = g.Spec + gr, err := k.crdClient.CrdV1alpha3().Groups(g.Namespace).Update(context.TODO(), gReturned, metav1.UpdateOptions{}) + return gr, err + } + return nil, fmt.Errorf("error occurred in creating/updating Group %s/%s", g.Namespace, g.Name) +} + func (data *TestData) GetV1Alpha3CG(cgName string) (*crdv1alpha3.ClusterGroup, error) { return data.crdClient.CrdV1alpha3().ClusterGroups().Get(context.TODO(), cgName, metav1.GetOptions{}) } @@ -720,6 +740,53 @@ func (data *TestData) CreateCG(name string, pSelector, nSelector *metav1.LabelSe return nil, fmt.Errorf("clustergroup with name %s already exists", name) } +// GetCG is a convenience function for getting ClusterGroups +func (k *KubernetesUtils) GetCG(name string) (*crdv1alpha2.ClusterGroup, error) { + res, err := k.crdClient.CrdV1alpha2().ClusterGroups().Get(context.TODO(), name, metav1.GetOptions{}) + if err != nil { + return nil, err + } + return res, nil +} + +// CreateGroup is a convenience function for creating an Antrea Group by namespace, name and selector. +func (k *KubernetesUtils) CreateGroup(namespace, name string, pSelector, nSelector *metav1.LabelSelector, ipBlocks []crdv1alpha1.IPBlock) (*crdv1alpha3.Group, error) { + log.Infof("Creating group %s/%s", namespace, name) + _, err := k.crdClient.CrdV1alpha3().Groups(namespace).Get(context.TODO(), name, metav1.GetOptions{}) + if err != nil { + g := &crdv1alpha3.Group{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + }, + } + if pSelector != nil { + g.Spec.PodSelector = pSelector + } + if nSelector != nil { + g.Spec.NamespaceSelector = nSelector + } + if len(ipBlocks) > 0 { + g.Spec.IPBlocks = ipBlocks + } + g, err = k.crdClient.CrdV1alpha3().Groups(namespace).Create(context.TODO(), g, metav1.CreateOptions{}) + if err != nil { + log.Debugf("Unable to create group %s/%s: %s", namespace, name, err) + } + return g, err + } + return nil, fmt.Errorf("group with name %s/%s already exists", namespace, name) +} + +// GetGroup is a convenience function for getting Groups +func (k *KubernetesUtils) GetGroup(namespace, name string) (*crdv1alpha3.Group, error) { + res, err := k.crdClient.CrdV1alpha3().Groups(namespace).Get(context.TODO(), name, metav1.GetOptions{}) + if err != nil { + return nil, err + } + return res, nil +} + // DeleteV1Alpha2CG is a convenience function for deleting crd/v1alpha2 ClusterGroup by name. func (data *TestData) DeleteV1Alpha2CG(name string) error { log.Infof("Deleting ClusterGroup %s", name) @@ -740,6 +807,16 @@ func (data *TestData) DeleteV1Alpha3CG(name string) error { return nil } +// DeleteV1Alpha3Group is a convenience function for deleting core/v1alpha3 Group by namespace and name. +func (k *KubernetesUtils) DeleteV1Alpha3Group(namespace, name string) error { + log.Infof("deleting Group %s/%s", namespace, name) + err := k.crdClient.CrdV1alpha3().Groups(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{}) + if err != nil { + return errors.Wrapf(err, "unable to delete Group %s/%s", namespace, name) + } + return nil +} + // CleanCGs is a convenience function for deleting all ClusterGroups in the cluster. func (data *TestData) CleanCGs() error { l, err := data.crdClient.CrdV1alpha2().ClusterGroups().List(context.TODO(), metav1.ListOptions{}) @@ -763,6 +840,20 @@ func (data *TestData) CleanCGs() error { return nil } +// CleanGroups is a convenience function for deleting all Groups in the namespace. +func (k *KubernetesUtils) CleanGroups(namespace string) error { + l, err := k.crdClient.CrdV1alpha3().Groups(namespace).List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return errors.Wrapf(err, "unable to list Groups in v1alpha3") + } + for _, g := range l.Items { + if err := k.DeleteV1Alpha3Group(namespace, g.Name); err != nil { + return err + } + } + return nil +} + // CreateOrUpdateACNP is a convenience function for updating/creating AntreaClusterNetworkPolicies. func (data *TestData) CreateOrUpdateACNP(cnp *crdv1alpha1.ClusterNetworkPolicy) (*crdv1alpha1.ClusterNetworkPolicy, error) { log.Infof("Creating/updating ClusterNetworkPolicy %s", cnp.Name) diff --git a/test/e2e/utils/anp_spec_builder.go b/test/e2e/utils/anp_spec_builder.go index cb11242f4f7..a5a5f5a0113 100644 --- a/test/e2e/utils/anp_spec_builder.go +++ b/test/e2e/utils/anp_spec_builder.go @@ -29,6 +29,7 @@ type AntreaNetworkPolicySpecBuilder struct { type ANPAppliedToSpec struct { PodSelector map[string]string PodSelectorMatchExp []metav1.LabelSelectorRequirement + Group string } func (b *AntreaNetworkPolicySpecBuilder) Get() *crdv1alpha1.NetworkPolicy { @@ -65,14 +66,14 @@ func (b *AntreaNetworkPolicySpecBuilder) SetTier(tier string) *AntreaNetworkPoli func (b *AntreaNetworkPolicySpecBuilder) SetAppliedToGroup(specs []ANPAppliedToSpec) *AntreaNetworkPolicySpecBuilder { for _, spec := range specs { - appliedToPeer := b.GetAppliedToPeer(spec.PodSelector, spec.PodSelectorMatchExp) + appliedToPeer := b.GetAppliedToPeer(spec.PodSelector, spec.PodSelectorMatchExp, spec.Group) b.Spec.AppliedTo = append(b.Spec.AppliedTo, appliedToPeer) } return b } func (b *AntreaNetworkPolicySpecBuilder) GetAppliedToPeer(podSelector map[string]string, - podSelectorMatchExp []metav1.LabelSelectorRequirement) crdv1alpha1.NetworkPolicyPeer { + podSelectorMatchExp []metav1.LabelSelectorRequirement, appliedToGrp string) crdv1alpha1.NetworkPolicyPeer { var ps *metav1.LabelSelector if len(podSelector) > 0 || len(podSelectorMatchExp) > 0 { ps = &metav1.LabelSelector{ @@ -80,16 +81,20 @@ func (b *AntreaNetworkPolicySpecBuilder) GetAppliedToPeer(podSelector map[string MatchExpressions: podSelectorMatchExp, } } - return crdv1alpha1.NetworkPolicyPeer{ + peer := crdv1alpha1.NetworkPolicyPeer{ PodSelector: ps, } + if appliedToGrp != "" { + peer.Group = appliedToGrp + } + return peer } func (b *AntreaNetworkPolicySpecBuilder) AddIngress(protoc AntreaPolicyProtocol, port *int32, portName *string, endPort, icmpType, icmpCode, igmpType *int32, groupAddress, cidr *string, podSelector map[string]string, nsSelector map[string]string, podSelectorMatchExp []metav1.LabelSelectorRequirement, nsSelectorMatchExp []metav1.LabelSelectorRequirement, - ruleAppliedToSpecs []ANPAppliedToSpec, action crdv1alpha1.RuleAction, name string) *AntreaNetworkPolicySpecBuilder { + ruleAppliedToSpecs []ANPAppliedToSpec, action crdv1alpha1.RuleAction, ruleGroup, name string) *AntreaNetworkPolicySpecBuilder { var ps, ns *metav1.LabelSelector var appliedTos []crdv1alpha1.NetworkPolicyPeer @@ -116,15 +121,16 @@ func (b *AntreaNetworkPolicySpecBuilder) AddIngress(protoc AntreaPolicyProtocol, } } for _, at := range ruleAppliedToSpecs { - appliedTos = append(appliedTos, b.GetAppliedToPeer(at.PodSelector, at.PodSelectorMatchExp)) + appliedTos = append(appliedTos, b.GetAppliedToPeer(at.PodSelector, at.PodSelectorMatchExp, at.Group)) } // An empty From/To in ANP rules evaluates to match all addresses. policyPeer := make([]crdv1alpha1.NetworkPolicyPeer, 0) - if ps != nil || ns != nil || ipBlock != nil { + if ps != nil || ns != nil || ipBlock != nil || ruleGroup != "" { policyPeer = []crdv1alpha1.NetworkPolicyPeer{{ PodSelector: ps, NamespaceSelector: ns, IPBlock: ipBlock, + Group: ruleGroup, }} } ports, protocols := GenPortsOrProtocols(protoc, port, portName, endPort, icmpType, icmpCode, igmpType, groupAddress) @@ -144,13 +150,13 @@ func (b *AntreaNetworkPolicySpecBuilder) AddEgress(protoc AntreaPolicyProtocol, port *int32, portName *string, endPort, icmpType, icmpCode, igmpType *int32, groupAddress, cidr *string, podSelector map[string]string, nsSelector map[string]string, podSelectorMatchExp []metav1.LabelSelectorRequirement, nsSelectorMatchExp []metav1.LabelSelectorRequirement, - ruleAppliedToSpecs []ANPAppliedToSpec, action crdv1alpha1.RuleAction, name string) *AntreaNetworkPolicySpecBuilder { + ruleAppliedToSpecs []ANPAppliedToSpec, action crdv1alpha1.RuleAction, ruleGroup, name string) *AntreaNetworkPolicySpecBuilder { // For simplicity, we just reuse the Ingress code here. The underlying data model for ingress/egress is identical // With the exception of calling the rule `To` vs. `From`. c := &AntreaNetworkPolicySpecBuilder{} c.AddIngress(protoc, port, portName, endPort, icmpType, icmpCode, igmpType, groupAddress, cidr, podSelector, nsSelector, - podSelectorMatchExp, nsSelectorMatchExp, ruleAppliedToSpecs, action, name) + podSelectorMatchExp, nsSelectorMatchExp, ruleAppliedToSpecs, action, ruleGroup, name) theRule := c.Get().Spec.Ingress[0] b.Spec.Egress = append(b.Spec.Egress, crdv1alpha1.Rule{ @@ -167,7 +173,7 @@ func (b *AntreaNetworkPolicySpecBuilder) AddToServicesRule(svcRefs []crdv1alpha1 name string, ruleAppliedToSpecs []ANPAppliedToSpec, action crdv1alpha1.RuleAction) *AntreaNetworkPolicySpecBuilder { var appliedTos []crdv1alpha1.NetworkPolicyPeer for _, at := range ruleAppliedToSpecs { - appliedTos = append(appliedTos, b.GetAppliedToPeer(at.PodSelector, at.PodSelectorMatchExp)) + appliedTos = append(appliedTos, b.GetAppliedToPeer(at.PodSelector, at.PodSelectorMatchExp, at.Group)) } newRule := crdv1alpha1.Rule{ To: make([]crdv1alpha1.NetworkPolicyPeer, 0), diff --git a/test/e2e/utils/grpspecbuilder.go b/test/e2e/utils/grpspecbuilder.go new file mode 100644 index 00000000000..1ba6be36683 --- /dev/null +++ b/test/e2e/utils/grpspecbuilder.go @@ -0,0 +1,100 @@ +// Copyright 2021 Antrea Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package utils + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + crdv1alpha1 "antrea.io/antrea/pkg/apis/crd/v1alpha1" + crdv1alpha3 "antrea.io/antrea/pkg/apis/crd/v1alpha3" +) + +// GroupSpecBuilder builds a Group object. +type GroupSpecBuilder struct { + Spec crdv1alpha3.GroupSpec + Name string + Namespace string +} + +func (b *GroupSpecBuilder) Get() *crdv1alpha3.Group { + return &crdv1alpha3.Group{ + ObjectMeta: metav1.ObjectMeta{ + Name: b.Name, + Namespace: b.Namespace, + }, + Spec: b.Spec, + } +} + +func (b *GroupSpecBuilder) SetName(name string) *GroupSpecBuilder { + b.Name = name + return b +} + +func (b *GroupSpecBuilder) SetNamespace(namespace string) *GroupSpecBuilder { + b.Namespace = namespace + return b +} + +func (b *GroupSpecBuilder) SetPodSelector(podSelector map[string]string, podSelectorMatchExp []metav1.LabelSelectorRequirement) *GroupSpecBuilder { + var ps *metav1.LabelSelector + if podSelector != nil { + ps = &metav1.LabelSelector{ + MatchLabels: podSelector, + } + if podSelectorMatchExp != nil { + ps.MatchExpressions = podSelectorMatchExp + } + } + b.Spec.PodSelector = ps + return b +} + +func (b *GroupSpecBuilder) SetNamespaceSelector(nsSelector map[string]string, nsSelectorMatchExp []metav1.LabelSelectorRequirement) *GroupSpecBuilder { + var ns *metav1.LabelSelector + if nsSelector != nil { + ns = &metav1.LabelSelector{ + MatchLabels: nsSelector, + } + if nsSelectorMatchExp != nil { + ns.MatchExpressions = nsSelectorMatchExp + } + } + b.Spec.NamespaceSelector = ns + return b +} + +func (b *GroupSpecBuilder) SetIPBlocks(ipBlocks []crdv1alpha1.IPBlock) *GroupSpecBuilder { + b.Spec.IPBlocks = ipBlocks + return b +} + +func (b *GroupSpecBuilder) SetServiceReference(svcNS, svcName string) *GroupSpecBuilder { + svcRef := &crdv1alpha1.NamespacedName{ + Namespace: svcNS, + Name: svcName, + } + b.Spec.ServiceReference = svcRef + return b +} + +func (b *GroupSpecBuilder) SetChildGroups(cgs []string) *GroupSpecBuilder { + var childGroups []crdv1alpha3.ClusterGroupReference + for _, c := range cgs { + childGroups = append(childGroups, crdv1alpha3.ClusterGroupReference(c)) + } + b.Spec.ChildGroups = childGroups + return b +}