Repository: antrea-io/antrea
Pull request #3257 (2022-01-27 05:36:26)

Update NPL and Secondary Network controllers initialization code
Change secondary network Pod controller to use the shared
localPodInfomer.

codecov-commenter: # [Codecov](https://codecov.io/gh/antrea-io/antrea/pull/3257?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) Report
> Merging [#3257](https://codecov.io/gh/antrea-io/antrea/pull/3257?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (68af61f) into [main](https://codecov.io/gh/antrea-io/antrea/commit/df6094bd0870a763d0d63b4fc69738a5370aa0de?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (df6094b) will **decrease** coverage by `15.55%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/antrea-io/antrea/pull/3257/graphs/tree.svg?width=650&height=150&src=pr&token=TjWaLL2GoC&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None)](https://codecov.io/gh/antrea-io/antrea/pull/3257?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None)

```diff
@@             Coverage Diff             @@
##             main    #3257       +/-   ##
===========================================
- Coverage   58.76%   43.20%   -15.56%     
===========================================
  Files         331      529      +198     
  Lines       28437    46609    +18172     
===========================================
+ Hits        16711    20138     +3427     
- Misses       9894    24294    +14400     
- Partials     1832     2177      +345     
```

| Flag | Coverage Δ | |
|---|---|---|
| integration-tests | `29.44% <ø> (?)` | |
| kind-e2e-tests | `33.70% <100.00%> (-11.93%)` | :arrow_down: |
| unit-tests | `41.26% <0.00%> (-0.11%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/antrea-io/antrea/pull/3257?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) | Coverage Δ | |
|---|---|---|
| [pkg/agent/nodeportlocal/k8s/npl\_controller.go](https://codecov.io/gh/antrea-io/antrea/pull/3257/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL2FnZW50L25vZGVwb3J0bG9jYWwvazhzL25wbF9jb250cm9sbGVyLmdv) | `60.83% <ø> (ø)` | |
| [pkg/agent/secondarynetwork/podwatch/controller.go](https://codecov.io/gh/antrea-io/antrea/pull/3257/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL2FnZW50L3NlY29uZGFyeW5ldHdvcmsvcG9kd2F0Y2gvY29udHJvbGxlci5nbw==) | `0.00% <ø> (ø)` | |
| [pkg/agent/nodeportlocal/npl\_agent\_init.go](https://codecov.io/gh/antrea-io/antrea/pull/3257/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL2FnZW50L25vZGVwb3J0bG9jYWwvbnBsX2FnZW50X2luaXQuZ28=) | `66.66% <100.00%> (-17.95%)` | :arrow_down: |
| [...g/agent/apiserver/handlers/featuregates/handler.go](https://codecov.io/gh/antrea-io/antrea/pull/3257/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL2FnZW50L2FwaXNlcnZlci9oYW5kbGVycy9mZWF0dXJlZ2F0ZXMvaGFuZGxlci5nbw==) | `0.00% <0.00%> (-82.36%)` | :arrow_down: |
| [pkg/agent/controller/networkpolicy/reject.go](https://codecov.io/gh/antrea-io/antrea/pull/3257/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL2FnZW50L2NvbnRyb2xsZXIvbmV0d29ya3BvbGljeS9yZWplY3QuZ28=) | `0.00% <0.00%> (-78.23%)` | :arrow_down: |
| [pkg/apis/controlplane/v1beta2/helper.go](https://codecov.io/gh/antrea-io/antrea/pull/3257/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL2FwaXMvY29udHJvbHBsYW5lL3YxYmV0YTIvaGVscGVyLmdv) | `33.33% <0.00%> (-66.67%)` | :arrow_down: |
| [pkg/agent/types/networkpolicy.go](https://codecov.io/gh/antrea-io/antrea/pull/3257/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL2FnZW50L3R5cGVzL25ldHdvcmtwb2xpY3kuZ28=) | `16.66% <0.00%> (-66.67%)` | :arrow_down: |
| [...formers/externalversions/security/v1alpha1/tier.go](https://codecov.io/gh/antrea-io/antrea/pull/3257/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL2xlZ2FjeWNsaWVudC9pbmZvcm1lcnMvZXh0ZXJuYWx2ZXJzaW9ucy9zZWN1cml0eS92MWFscGhhMS90aWVyLmdv) | `0.00% <0.00%> (-64.29%)` | :arrow_down: |
| [...ers/externalversions/core/v1alpha2/clustergroup.go](https://codecov.io/gh/antrea-io/antrea/pull/3257/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL2xlZ2FjeWNsaWVudC9pbmZvcm1lcnMvZXh0ZXJuYWx2ZXJzaW9ucy9jb3JlL3YxYWxwaGEyL2NsdXN0ZXJncm91cC5nbw==) | `0.00% <0.00%> (-64.29%)` | :arrow_down: |
| [...s/externalversions/core/v1alpha2/externalentity.go](https://codecov.io/gh/antrea-io/antrea/pull/3257/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL2xlZ2FjeWNsaWVudC9pbmZvcm1lcnMvZXh0ZXJuYWx2ZXJzaW9ucy9jb3JlL3YxYWxwaGEyL2V4dGVybmFsZW50aXR5Lmdv) | `0.00% <0.00%> (-64.29%)` | :arrow_down: |
| ... and [293 more](https://codecov.io/gh/antrea-io/antrea/pull/3257/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) | |


Patch:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index b70493048..dd6fdc760 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -5,14 +5,10 @@ on:
     branches:
       - main
       - release-*
-      # TODO: remove this before merging feature branch to main
-      - feature/multi-cluster
   push:
     branches:
       - main
       - release-*
-      # TODO: remove this before merging feature branch to main
-      - feature/multi-cluster
 
 jobs:
   check-changes:
@@ -120,7 +116,7 @@ jobs:
     - name: Build antrea-mc-controller Docker image
       run: make antrea-mc-controller
     - name: Push antrea-mc-controller Docker image to registry
-      if: ${{ github.repository == 'antrea-io/antrea' && github.event_name == 'push' && github.ref == 'refs/heads/feature/multi-cluster' }}
+      if: ${{ github.repository == 'antrea-io/antrea' && github.event_name == 'push' && github.ref == 'refs/heads/main' }}
       env:
         DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
         DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index ec2f50fd1..970d42b2d 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -4,12 +4,10 @@ on:
     branches:
     - main
     - release-*
-    - feature/multi-cluster
   push:
     branches:
     - main
     - release-*
-    - feature/multi-cluster
 
 env:
   go-cache-name: go
diff --git a/CHANGELOG/CHANGELOG-1.5.md b/CHANGELOG/CHANGELOG-1.5.md
index bfda6c555..0c2dc181d 100644
--- a/CHANGELOG/CHANGELOG-1.5.md
+++ b/CHANGELOG/CHANGELOG-1.5.md
@@ -16,7 +16,7 @@
 - Add support for SR-IOV secondary network. Antrea can now create secondary network interfaces for Pods using SR-IOV VFs on bare metal Nodes. ([#2651](https://github.com/antrea-io/antrea/pull/2651), [@arunvelayutham]) [Alpha - Feature Gate: `SecondaryNetwork`]
 - Add support for allocating external IPs for Services of type LoadBalancer from an ExternalIPPool. ([#3147](https://github.com/antrea-io/antrea/pull/3147) [@Shengkai2000]) [Alpha - Feature Gate: `ServiceExternalIP`]
 - Add support for antctl in the flow aggregator Pod. ([#2878](https://github.com/antrea-io/antrea/pull/2878), [@yanjunz97])
-  * Support `antctl get log-level` for changing log verbosity level
+  * Support `antctl log-level` for changing log verbosity level
   * Support `antctl get flowrecords [-o json]` for dumping flow records
   * Support `antctl get recordmetrics` for dumping flow records metrics
 - Add support for the "Pass" action in Antrea-native policies to skip evaluation of further Antrea-native policy rules and delegate evaluation to Kubernetes NetworkPolicy. ([#2964](https://github.com/antrea-io/antrea/pull/2964), [@Dyanngg])
diff --git a/CHANGELOG/README.md b/CHANGELOG/README.md
index 2075b67c4..a97ab1da8 100644
--- a/CHANGELOG/README.md
+++ b/CHANGELOG/README.md
@@ -10,6 +10,7 @@ stages](https://github.com/kubernetes/community/blob/master/contributors/devel/s
 Some experimental features can be enabled / disabled using [Feature
 Gates](../docs/feature-gates.md).
 
+- [CHANGELOG-1.5](CHANGELOG-1.5.md)
 - [CHANGELOG-1.4](CHANGELOG-1.4.md)
 - [CHANGELOG-1.3](CHANGELOG-1.3.md)
 - [CHANGELOG-1.2](CHANGELOG-1.2.md)
diff --git a/build/images/scripts/install_cni b/build/images/scripts/install_cni
index a464662ef..569523064 100755
--- a/build/images/scripts/install_cni
+++ b/build/images/scripts/install_cni
@@ -4,17 +4,9 @@ set -euo pipefail
 
 # Todo: check version and continue installation only for a newer version
 
-# Install Antrea configuration file
-install -m 644 /etc/antrea/antrea-cni.conflist /host/etc/cni/net.d/10-antrea.conflist
-
 # Install Antrea binary file
 install -m 755 /usr/local/bin/antrea-cni /host/opt/cni/bin/antrea
 
-# If more than one CNI config file exists, the file with the lowest name is
-# chosen i.e. existing 10-antrea.conf will be chosen over 10-antrea.conflist.
-# Hence, delete older 10-antrea.conf file.
-rm -f /host/etc/cni/net.d/10-antrea.conf
-
 # Install the loopback plugin.
 # It is required by kubelet on Linux when using docker as the container runtime.
 # We replace the binary files even they are already present on the Node to make
@@ -30,6 +22,16 @@ install -m 755 /opt/cni/bin/bandwidth /host/opt/cni/bin/bandwidth
 # Install whereabouts IPAM binary file. Required for global IPAM support specific to CNF use cases.
 install -m 755 /opt/cni/bin/whereabouts /host/opt/cni/bin/whereabouts
 
+# Install Antrea configuration file.
+# Note that it needs to be executed after installing the above binaries because container runtimes such as cri-o may
+# watch the conf directory and try to validate the config and binaries immediately once there is a change.
+install -m 644 /etc/antrea/antrea-cni.conflist /host/etc/cni/net.d/10-antrea.conflist
+
+# If more than one CNI config file exists, the file with the lowest name is
+# chosen i.e. existing 10-antrea.conf will be chosen over 10-antrea.conflist.
+# Hence, delete older 10-antrea.conf file.
+rm -f /host/etc/cni/net.d/10-antrea.conf
+
 # Load the OVS kernel module
 modprobe openvswitch || (echo "Failed to load the OVS kernel module from the container, try running 'modprobe openvswitch' on your Nodes"; exit 1)
 
diff --git a/cmd/antrea-agent/agent.go b/cmd/antrea-agent/agent.go
index c80035c4d..eecb52fce 100644
--- a/cmd/antrea-agent/agent.go
+++ b/cmd/antrea-agent/agent.go
@@ -451,7 +451,9 @@ func run(o *Options) error {
 
 	// Initialize localPodInformer for NPLAgent and AntreaIPAMController
 	var localPodInformer cache.SharedIndexInformer
-	if features.DefaultFeatureGate.Enabled(features.NodePortLocal) && o.config.NodePortLocal.Enable || features.DefaultFeatureGate.Enabled(features.AntreaIPAM) {
+	if features.DefaultFeatureGate.Enabled(features.NodePortLocal) && o.config.NodePortLocal.Enable ||
+		features.DefaultFeatureGate.Enabled(features.AntreaIPAM) ||
+		features.DefaultFeatureGate.Enabled(features.SecondaryNetwork) {
 		listOptions := func(options *metav1.ListOptions) {
 			options.FieldSelector = fields.OneTermEqualSelector("spec.nodeName", nodeConfig.Name).String()
 		}
@@ -463,6 +465,19 @@ func run(o *Options) error {
 			listOptions,
 		)
 	}
+
+	log.StartLogFileNumberMonitor(stopCh)
+
+	go routeClient.Run(stopCh)
+
+	go cniServer.Run(stopCh)
+
+	go antreaClientProvider.Run(stopCh)
+
+	go nodeRouteController.Run(stopCh)
+
+	go networkPolicyController.Run(stopCh)
+
 	// Initialize the NPL agent.
 	if features.DefaultFeatureGate.Enabled(features.NodePortLocal) && o.config.NodePortLocal.Enable {
 		nplController, err := npl.InitializeNPLAgent(
@@ -477,6 +492,7 @@ func run(o *Options) error {
 		}
 		go nplController.Run(stopCh)
 	}
+
 	// Initialize the Antrea IPAM agent.
 	if features.DefaultFeatureGate.Enabled(features.AntreaIPAM) {
 		ipamController, err := ipam.InitializeAntreaIPAMController(
@@ -490,25 +506,6 @@ func run(o *Options) error {
 		}
 		go ipamController.Run(stopCh)
 	}
-	//  Start the localPodInformer
-	if localPodInformer != nil {
-		go localPodInformer.Run(stopCh)
-	}
-
-	log.StartLogFileNumberMonitor(stopCh)
-
-	go routeClient.Run(stopCh)
-
-	go cniServer.Run(stopCh)
-
-	informerFactory.Start(stopCh)
-	crdInformerFactory.Start(stopCh)
-
-	go antreaClientProvider.Run(stopCh)
-
-	go nodeRouteController.Run(stopCh)
-
-	go networkPolicyController.Run(stopCh)
 
 	if features.DefaultFeatureGate.Enabled(features.SecondaryNetwork) {
 		// Create the NetworkAttachmentDefinition client, which handles access to secondary network object definition from the API Server.
@@ -516,20 +513,25 @@ func run(o *Options) error {
 		if err != nil {
 			return fmt.Errorf("NetworkAttachmentDefinition client creation failed. %v", err)
 		}
-		// Initialize podController to handle secondary network configuration for Pods with k8s.v1.cni.cncf.io/networks Annotation defined.
-		podWatchController, err := podwatch.InitializePodController(
+		// Create podController to handle secondary network configuration for Pods with k8s.v1.cni.cncf.io/networks Annotation defined.
+		podWatchController := podwatch.NewPodController(
 			k8sClient,
 			netAttachDefClient,
-			informerFactory,
+			localPodInformer,
 			nodeConfig.Name,
 			cniPodInfoStore,
 			cniServer)
-		if err != nil {
-			return fmt.Errorf("failed to initialize Pod Controller for secondary network interface management: %v", err)
-		}
 		go podWatchController.Run(stopCh)
 	}
 
+	//  Start the localPodInformer
+	if localPodInformer != nil {
+		go localPodInformer.Run(stopCh)
+	}
+
+	informerFactory.Start(stopCh)
+	crdInformerFactory.Start(stopCh)
+
 	if features.DefaultFeatureGate.Enabled(features.Egress) || features.DefaultFeatureGate.Enabled(features.ServiceExternalIP) {
 		go externalIPPoolController.Run(stopCh)
 		go localIPDetector.Run(stopCh)
diff --git a/docs/antrea-network-policy.md b/docs/antrea-network-policy.md
index e6b6e5a72..35eede834 100644
--- a/docs/antrea-network-policy.md
+++ b/docs/antrea-network-policy.md
@@ -836,7 +836,7 @@ status:
       lastTransitionTime: "2021-01-29T20:21:48Z"
 ```
 
-There are a few __restrictions__ on how ClusterGroups can be configured:
+There are a few **restrictions** on how ClusterGroups can be configured:
 
 - A ClusterGroup is a cluster-scoped resource and therefore can only be set in an Antrea
   ClusterNetworkPolicy's `appliedTo` and `to`/`from` peers.
diff --git a/docs/multicluster/getting-started.md b/docs/multicluster/getting-started.md
index 70eb5356d..8d1508646 100644
--- a/docs/multicluster/getting-started.md
+++ b/docs/multicluster/getting-started.md
@@ -36,7 +36,7 @@ run both member and leader controllers in one cluster.
      kubectl apply -f https://github.com/antrea-io/antrea/releases/download/<TAG>/antrea-multicluster-leader-global.yml
      ```
 
-   * To deploy the latest version (checkout and built from the `feature/multi-cluster` branch).
+   * To deploy the latest version (checkout and built from the `main` branch).
 
      ```bash
      kubectl apply -f multicluster/build/yamls/antrea-multicluster-leader-global.yml
@@ -54,7 +54,7 @@ manifest with new Namespace.
   sed 's/changeme/antrea-mcs-ns/g' antrea-multicluster-leader-namespaced.yml | kubectl apply -f -
   ```
   
-* To deploy the latest version (checkout and built from the `feature/multi-cluster` branch).
+* To deploy the latest version (checkout and built from the `main` branch).
   
   ```bash
   kubectl create ns antrea-mcs-ns
@@ -73,7 +73,7 @@ Namespace by default.
 kubectl apply -f https://github.com/antrea-io/antrea/releases/download/<TAG>/antrea-multicluster-member.yml
 ```
 
-* To deploy the latest version (checkout and built from the `feature/multi-cluster` branch).
+* To deploy the latest version (checkout and built from the `main` branch).
 
 ```bash
 kubectl apply -f multicluster/build/yamls/antrea-multicluster-member.yml
diff --git a/docs/node-port-local.md b/docs/node-port-local.md
index 03c2ab064..156a23361 100644
--- a/docs/node-port-local.md
+++ b/docs/node-port-local.md
@@ -121,6 +121,11 @@ metadata:
 This annotation indicates that port 8080 of the Pod can be reached through port
 61002 of the Node with IP Address 10.10.10.10.
 
+The `nodeportlocal.antrea.io` annotation is generated and managed by Antrea. It
+is not meant to be created or modified by users directly. A user-provided
+annotation is likely to be overwritten by Antrea, or may lead to unexpected
+behavior.
+
 NodePortLocal can only be used with Services of type `ClusterIP` or
 `LoadBalancer`. The `nodeportlocal.antrea.io` annotation has no effect for
 Services of type `NodePort` or `ExternalName`. The annotation also has no effect
diff --git a/docs/windows.md b/docs/windows.md
index bf1651c7b..6d35e84b3 100644
--- a/docs/windows.md
+++ b/docs/windows.md
@@ -184,6 +184,13 @@ spec:
       hostNetwork: true
 ```
 
+If the powershell version on your Window Node is earlier than v6.0,
+change the command from `pwsh` to `powershell` in kube-proxy.yml.
+
+```bash
+sed -i 's/pwsh/powershell/g' kube-proxy.yml
+```
+
 Then apply the `kube-proxy.yml`.
 
 ```bash
diff --git a/pkg/agent/controller/noderoute/node_route_controller.go b/pkg/agent/controller/noderoute/node_route_controller.go
index 543368775..fd9e96910 100644
--- a/pkg/agent/controller/noderoute/node_route_controller.go
+++ b/pkg/agent/controller/noderoute/node_route_controller.go
@@ -532,7 +532,7 @@ func (c *Controller) addNodeRoute(nodeName string, node *corev1.Node) error {
 
 		peerPodCIDRAddr, peerPodCIDR, err := net.ParseCIDR(podCIDR)
 		if err != nil {
-			klog.Errorf("Failed to parse PodCIDR %s for Node %s", node.Spec.PodCIDR, nodeName)
+			klog.Errorf("Failed to parse PodCIDR %s for Node %s", podCIDR, nodeName)
 			return nil
 		}
 		peerGatewayIP := ip.NextIP(peerPodCIDRAddr)
diff --git a/pkg/agent/controller/serviceexternalip/controller.go b/pkg/agent/controller/serviceexternalip/controller.go
index 0327ce933..7c28e236e 100644
--- a/pkg/agent/controller/serviceexternalip/controller.go
+++ b/pkg/agent/controller/serviceexternalip/controller.go
@@ -201,7 +201,11 @@ func (c *ServiceExternalIPController) enqueueServiceForEndpoints(obj interface{}
 	}
 	service, err := c.serviceLister.Services(endpoints.Namespace).Get(endpoints.Name)
 	if err != nil {
-		klog.ErrorS(err, "failed to get Service for Endpoint", "namespace", endpoints.Namespace, "name", endpoints.Name)
+		// The only possible error Lister.Get can return is NotFound.
+		// It's normal that some Endpoints don't have Service. For example, kube-scheduler and kube-controller-manager
+		// may use Endpoints for leader election. Even if the Endpoints should have a Service but it's not received yet,
+		// it's fine to ignore the error as the Service's add event will enqueue it.
+		klog.V(5).InfoS("Failed to get Service for Endpoints", "Endpoints", klog.KObj(endpoints), "err", err)
 		return
 	}
 	// we only care services with ServiceExternalTrafficPolicy setting to local.
diff --git a/pkg/agent/nodeportlocal/k8s/npl_controller.go b/pkg/agent/nodeportlocal/k8s/npl_controller.go
index 67225cc38..ba152354a 100644
--- a/pkg/agent/nodeportlocal/k8s/npl_controller.go
+++ b/pkg/agent/nodeportlocal/k8s/npl_controller.go
@@ -42,10 +42,14 @@ import (
 )
 
 const (
-	controllerName = "AntreaAgentNPLController"
+	controllerName = "NPLController"
 	minRetryDelay  = 2 * time.Second
 	maxRetryDelay  = 120 * time.Second
 	numWorkers     = 4
+
+	// Set resyncPeriod to 0 to disable resyncing.
+	// UpdateFunc event handler will be called only when the object is actually updated.
+	resyncPeriod = 0 * time.Minute
 )
 
 type NPLController struct {
@@ -63,7 +67,6 @@ type NPLController struct {
 func NewNPLController(kubeClient clientset.Interface,
 	podInformer cache.SharedIndexInformer,
 	svcInformer cache.SharedIndexInformer,
-	resyncPeriod time.Duration,
 	pt *portcache.PortTable,
 	nodeName string) *NPLController {
 	c := NPLController{
@@ -191,6 +194,15 @@ func (c *NPLController) checkDeletedSvc(obj interface{}) (*corev1.Service, error
 func validateNPLService(svc *corev1.Service) {
 	if svc.Spec.Type == corev1.ServiceTypeNodePort {
 		klog.InfoS("Service is of type NodePort and cannot be used for NodePortLocal, the NodePortLocal annotation will have no effect", "service", klog.KObj(svc))
+		return
+	}
+	if svc.Spec.Type == corev1.ServiceTypeExternalName {
+		klog.InfoS("Service is of type ExternalName and cannot be used for NodePortLocal, the NodePortLocal annotation will have no effect", "service", klog.KObj(svc))
+		return
+	}
+	if len(svc.Spec.Selector) == 0 {
+		klog.InfoS("Service does not have a selector, the NodePortLocal annotation will have no effect", "service", klog.KObj(svc))
+		return
 	}
 	for _, port := range svc.Spec.Ports {
 		if port.Protocol == corev1.ProtocolSCTP {
@@ -217,8 +229,8 @@ func (c *NPLController) enqueueSvcUpdate(oldObj, newObj interface{}) {
 	}
 
 	podKeys := sets.String{}
-	oldNPLEnabled := oldSvcAnnotation == "true" && oldSvc.Spec.Type != corev1.ServiceTypeNodePort
-	newNPLEnabled := newSvcAnnotation == "true" && newSvc.Spec.Type != corev1.ServiceTypeNodePort
+	oldNPLEnabled := oldSvcAnnotation == "true" && oldSvc.Spec.Type != corev1.ServiceTypeNodePort && oldSvc.Spec.Type != corev1.ServiceTypeExternalName
+	newNPLEnabled := newSvcAnnotation == "true" && newSvc.Spec.Type != corev1.ServiceTypeNodePort && newSvc.Spec.Type != corev1.ServiceTypeExternalName
 
 	if oldNPLEnabled != newNPLEnabled {
 		// Process Pods corresponding to Service with valid NPL annotation and Service type.
@@ -297,27 +309,27 @@ func (c *NPLController) getTargetPortsForServicesOfPod(obj interface{}) (sets.St
 	for _, service := range services {
 		svc, isSvc := service.(*corev1.Service)
 		// Selecting Services NOT of type NodePort, with Service selector matching Pod labels.
-		if isSvc && svc.Spec.Type != corev1.ServiceTypeNodePort {
-			if pod.Namespace == svc.Namespace &&
-				matchSvcSelectorPodLabels(svc.Spec.Selector, pod.GetLabels()) {
-				for _, port := range svc.Spec.Ports {
-					if port.Protocol == corev1.ProtocolSCTP {
-						// Not supported yet. A message is logged when the
-						// Service is processed.
-						continue
-					}
-					switch port.TargetPort.Type {
-					case intstr.Int:
-						// An entry of format <target-port>:<protocol> (e.g. 8080:TCP) is added for a target port in the set targetPortsInt.
-						// This is done to ensure that we can match with both port and protocol fields in container port of a Pod.
-						portProto := util.BuildPortProto(fmt.Sprint(port.TargetPort.IntVal), string(port.Protocol))
-						klog.V(4).Infof("Added target port in targetPortsInt set: %v", portProto)
-						targetPortsInt.Insert(portProto)
-					case intstr.String:
-						portProto := util.BuildPortProto(port.TargetPort.StrVal, string(port.Protocol))
-						klog.V(4).Infof("Added target port in targetPortsStr set: %v", portProto)
-						targetPortsStr.Insert(portProto)
-					}
+		if !isSvc || svc.Spec.Type == corev1.ServiceTypeNodePort || svc.Spec.Type == corev1.ServiceTypeExternalName {
+			continue
+		}
+		if pod.Namespace == svc.Namespace && matchSvcSelectorPodLabels(svc.Spec.Selector, pod.GetLabels()) {
+			for _, port := range svc.Spec.Ports {
+				if port.Protocol == corev1.ProtocolSCTP {
+					// Not supported yet. A message is logged when the
+					// Service is processed.
+					continue
+				}
+				switch port.TargetPort.Type {
+				case intstr.Int:
+					// An entry of format <target-port>:<protocol> (e.g. 8080:TCP) is added for a target port in the set targetPortsInt.
+					// This is done to ensure that we can match with both port and protocol fields in container port of a Pod.
+					portProto := util.BuildPortProto(fmt.Sprint(port.TargetPort.IntVal), string(port.Protocol))
+					klog.V(4).Infof("Added target port in targetPortsInt set: %v", portProto)
+					targetPortsInt.Insert(portProto)
+				case intstr.String:
+					portProto := util.BuildPortProto(port.TargetPort.StrVal, string(port.Protocol))
+					klog.V(4).Infof("Added target port in targetPortsStr set: %v", portProto)
+					targetPortsStr.Insert(portProto)
 				}
 			}
 		}
diff --git a/pkg/agent/nodeportlocal/npl_agent_init.go b/pkg/agent/nodeportlocal/npl_agent_init.go
index fb0336913..b2b09ef42 100644
--- a/pkg/agent/nodeportlocal/npl_agent_init.go
+++ b/pkg/agent/nodeportlocal/npl_agent_init.go
@@ -19,7 +19,6 @@ package nodeportlocal
 
 import (
 	"fmt"
-	"time"
 
 	nplk8s "antrea.io/antrea/pkg/agent/nodeportlocal/k8s"
 	"antrea.io/antrea/pkg/agent/nodeportlocal/portcache"
@@ -29,10 +28,6 @@ import (
 	"k8s.io/client-go/tools/cache"
 )
 
-// Set resyncPeriod to 0 to disable resyncing.
-// UpdateFunc event handler will be called only when the object is actually updated.
-const resyncPeriod = 0 * time.Minute
-
 // InitializeNPLAgent initializes the NodePortLocal agent.
 // It sets up event handlers to handle Pod add, update and delete events.
 // When a Pod gets created, a free Node port is obtained from the port table cache and a DNAT rule is added to NAT traffic to the Pod's ip:port.
@@ -49,20 +44,6 @@ func InitializeNPLAgent(
 		return nil, fmt.Errorf("error when initializing NodePortLocal port table: %v", err)
 	}
 
-	return InitController(kubeClient, informerFactory, portTable, nodeName, podInformer)
-}
-
-// InitController initializes the NPLController with appropriate Pod and Service Informers.
-// This function can be used independently while unit testing without using InitializeNPLAgent function.
-func InitController(kubeClient clientset.Interface, informerFactory informers.SharedInformerFactory, portTable *portcache.PortTable, nodeName string, podInformer cache.SharedIndexInformer) (*nplk8s.NPLController, error) {
 	svcInformer := informerFactory.Core().V1().Services().Informer()
-
-	c := nplk8s.NewNPLController(kubeClient,
-		podInformer,
-		svcInformer,
-		resyncPeriod,
-		portTable,
-		nodeName)
-
-	return c, nil
+	return nplk8s.NewNPLController(kubeClient, podInformer, svcInformer, portTable, nodeName), nil
 }
diff --git a/pkg/agent/route/route_linux.go b/pkg/agent/route/route_linux.go
index a62e8f721..67de65a80 100644
--- a/pkg/agent/route/route_linux.go
+++ b/pkg/agent/route/route_linux.go
@@ -694,7 +694,7 @@ func (c *Client) Reconcile(podCIDRs []string, svcIPs map[string]bool) error {
 			if desiredPodCIDRs.Has(entry) {
 				continue
 			}
-			klog.Infof("Deleting orphaned PodIP %s from ipset and route table", entry)
+			klog.Infof("Deleting orphaned Pod IP %s from ipset and route table", entry)
 			if err := ipset.DelEntry(ipsetName, entry); err != nil {
 				return err
 			}
@@ -748,7 +748,7 @@ func (c *Client) Reconcile(podCIDRs []string, svcIPs map[string]bool) error {
 		if desiredGWs.Has(neighIP) {
 			continue
 		}
-		// Don't delete the virtual Service IP neigh which is added by AntreaProxy.
+		// Don't delete the virtual Service IP neighbor which is added by AntreaProxy.
 		if actualNeigh.IP.Equal(config.VirtualServiceIPv6) {
 			continue
 		}
@@ -1157,7 +1157,7 @@ func (c *Client) AddClusterIPRoute(svcIP net.IP) error {
 			} else {
 				c.clusterIPv4CIDR = newClusterIPCIDR
 			}
-			klog.V(4).InfoS("Created a route with new CLusterIP CIDR to route the ClusterIP to Antrea gateway", "CLusterIP CIDR", newClusterIPCIDR, "clusterIP", svcIP)
+			klog.V(4).InfoS("Created a route with new ClusterIP CIDR to route the ClusterIP to Antrea gateway", "ClusterIP CIDR", newClusterIPCIDR, "clusterIP", svcIP)
 		} else {
 			klog.V(4).InfoS("Route with current ClusterIP CIDR can route the ClusterIP to Antrea gateway", "ClusterIP CIDR", curClusterIPCIDR, "clusterIP", svcIP)
 		}
diff --git a/pkg/agent/secondarynetwork/podwatch/controller.go b/pkg/agent/secondarynetwork/podwatch/controller.go
index bbf4746b6..f92441876 100644
--- a/pkg/agent/secondarynetwork/podwatch/controller.go
+++ b/pkg/agent/secondarynetwork/podwatch/controller.go
@@ -26,11 +26,8 @@ import (
 
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/fields"
 	"k8s.io/apimachinery/pkg/util/rand"
 	"k8s.io/apimachinery/pkg/util/wait"
-	"k8s.io/client-go/informers"
-	coreinformers "k8s.io/client-go/informers/core/v1"
 	clientset "k8s.io/client-go/kubernetes"
 	corelisters "k8s.io/client-go/listers/core/v1"
 	"k8s.io/client-go/tools/cache"
@@ -81,7 +78,6 @@ type PodController struct {
 func NewPodController(kubeClient clientset.Interface,
 	netAttachDefClient netdefclient.K8sCniCncfIoV1Interface,
 	podInformer cache.SharedIndexInformer,
-	resyncPeriod time.Duration,
 	nodeName string,
 	podCache cnipodcache.CNIPodInfoStore,
 	cniServer *cniserver.CNIServer) *PodController {
@@ -398,26 +394,3 @@ func parsePodSecondaryNetworkAnnotation(netObj string) ([]*SecondaryNetworkObjec
 	}
 	return secNetwork, nil
 }
-
-func InitializePodController(kubeClient clientset.Interface, netAttachDefClient netdefclient.K8sCniCncfIoV1Interface, informerFactory informers.SharedInformerFactory,
-	nodeName string, podInfoStore cnipodcache.CNIPodInfoStore, cniServer *cniserver.CNIServer) (*PodController, error) {
-	// Watch only the Pods which belong to the Node where the agent is running.
-	listOptions := func(options *metav1.ListOptions) {
-		options.FieldSelector = fields.OneTermEqualSelector("spec.nodeName", nodeName).String()
-	}
-	podInformer := coreinformers.NewFilteredPodInformer(
-		kubeClient,
-		metav1.NamespaceAll,
-		resyncPeriod,
-		cache.Indexers{},
-		listOptions,
-	)
-	c := NewPodController(kubeClient,
-		netAttachDefClient,
-		podInformer,
-		resyncPeriod,
-		nodeName,
-		podInfoStore,
-		cniServer)
-	return c, nil
-}


Test patch:
diff --git a/ci/kind/test-e2e-kind.sh b/ci/kind/test-e2e-kind.sh
index 0341637bd..e6820860b 100755
--- a/ci/kind/test-e2e-kind.sh
+++ b/ci/kind/test-e2e-kind.sh
@@ -127,7 +127,7 @@ fi
 
 COMMON_IMAGES_LIST=("k8s.gcr.io/e2e-test-images/agnhost:2.29" \
                     "projects.registry.vmware.com/library/busybox"  \
-                    "projects.registry.vmware.com/antrea/nginx" \
+                    "projects.registry.vmware.com/antrea/nginx:1.21.6-alpine" \
                     "projects.registry.vmware.com/antrea/perftool" \
                     "projects.registry.vmware.com/antrea/ipfix-collector:v0.5.11" \
                     "projects.registry.vmware.com/antrea/wireguard-go:0.0.20210424")
diff --git a/pkg/agent/nodeportlocal/npl_agent_test.go b/pkg/agent/nodeportlocal/npl_agent_test.go
index 8d71e807b..c577e7e99 100644
--- a/pkg/agent/nodeportlocal/npl_agent_test.go
+++ b/pkg/agent/nodeportlocal/npl_agent_test.go
@@ -238,6 +238,7 @@ func setUp(t *testing.T, tc *testConfig, objects ...runtime.Object) *testData {
 		portTable: newPortTable(mockIPTables, mockPortOpener),
 	}
 
+	resyncPeriod := 0 * time.Minute
 	// informerFactory is initialized and started from cmd/antrea-agent/agent.go
 	informerFactory := informers.NewSharedInformerFactory(data.k8sClient, resyncPeriod)
 	listOptions := func(options *metav1.ListOptions) {
@@ -250,9 +251,9 @@ func setUp(t *testing.T, tc *testConfig, objects ...runtime.Object) *testData {
 		cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, // NamespaceIndex is used in NPLController.
 		listOptions,
 	)
+	svcInformer := informerFactory.Core().V1().Services().Informer()
 
-	c, err := InitController(data.k8sClient, informerFactory, data.portTable, defaultNodeName, localPodInformer)
-	require.NoError(t, err)
+	c := nplk8s.NewNPLController(data.k8sClient, localPodInformer, svcInformer, data.portTable, defaultNodeName)
 
 	data.runWrapper(c)
 	informerFactory.Start(data.stopCh)
diff --git a/pkg/controller/networkpolicy/validate_test.go b/pkg/controller/networkpolicy/validate_test.go
new file mode 100644
index 000000000..d5f65466f
--- /dev/null
+++ b/pkg/controller/networkpolicy/validate_test.go
@@ -0,0 +1,957 @@
+// 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 (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	admv1 "k8s.io/api/admission/v1"
+	authenticationv1 "k8s.io/api/authentication/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	crdv1alpha1 "antrea.io/antrea/pkg/apis/crd/v1alpha1"
+)
+
+func TestValidateAntreaPolicy(t *testing.T) {
+	allowAction := crdv1alpha1.RuleActionAllow
+	passAction := crdv1alpha1.RuleActionPass
+	int32For80 := int32(80)
+
+	tests := []struct {
+		name           string
+		policy         *crdv1alpha1.ClusterNetworkPolicy
+		expectedReason string
+	}{
+		{
+			name: "acnp-non-existent-tier",
+			policy: &crdv1alpha1.ClusterNetworkPolicy{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "non-existent-tier",
+				},
+				Spec: crdv1alpha1.ClusterNetworkPolicySpec{
+					AppliedTo: []crdv1alpha1.NetworkPolicyPeer{
+						{
+							PodSelector: &metav1.LabelSelector{
+								MatchLabels: map[string]string{"foo": "bar"},
+							},
+						},
+					},
+					Tier: "non-existent-tier",
+				},
+			},
+			expectedReason: "tier non-existent-tier does not exist",
+		},
+		{
+			name: "acnp-static-tier",
+			policy: &crdv1alpha1.ClusterNetworkPolicy{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "acnp-static-tier",
+				},
+				Spec: crdv1alpha1.ClusterNetworkPolicySpec{
+					AppliedTo: []crdv1alpha1.NetworkPolicyPeer{
+						{
+							PodSelector: &metav1.LabelSelector{
+								MatchLabels: map[string]string{"foo": "bar"},
+							},
+						},
+					},
+					Tier: "NetworkOps",
+				},
+			},
+			expectedReason: "",
+		},
+		{
+			name: "acnp-ingress-baseline-pass-action",
+			policy: &crdv1alpha1.ClusterNetworkPolicy{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "acnp-ingress-baseline-pass-action",
+				},
+				Spec: crdv1alpha1.ClusterNetworkPolicySpec{
+					AppliedTo: []crdv1alpha1.NetworkPolicyPeer{
+						{
+							PodSelector: &metav1.LabelSelector{
+								MatchLabels: map[string]string{"foo1": "bar1"},
+							},
+						},
+					},
+					Tier: "Baseline",
+					Ingress: []crdv1alpha1.Rule{
+						{
+							Action: &passAction,
+							From: []crdv1alpha1.NetworkPolicyPeer{
+								{
+									PodSelector: &metav1.LabelSelector{
+										MatchLabels: map[string]string{"foo2": "bar2"},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			expectedReason: "`Pass` action should not be set for Baseline Tier policy rules",
+		},
+		{
+			name: "acnp-egress-baseline-pass-action",
+			policy: &crdv1alpha1.ClusterNetworkPolicy{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "acnp-egress-baseline-pass-action",
+				},
+				Spec: crdv1alpha1.ClusterNetworkPolicySpec{
+					AppliedTo: []crdv1alpha1.NetworkPolicyPeer{
+						{
+							PodSelector: &metav1.LabelSelector{
+								MatchLabels: map[string]string{"foo1": "bar1"},
+							},
+						},
+					},
+					Tier: "Baseline",
+					Egress: []crdv1alpha1.Rule{
+						{
+							Action: &passAction,
+							To: []crdv1alpha1.NetworkPolicyPeer{
+								{
+									PodSelector: &metav1.LabelSelector{
+										MatchLabels: map[string]string{"foo2": "bar2"},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			expectedReason: "`Pass` action should not be set for Baseline Tier policy rules",
+		},
+		{
+			name: "acnp-egress-pass-action",
+			policy: &crdv1alpha1.ClusterNetworkPolicy{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "acnp-egress-pass-action",
+				},
+				Spec: crdv1alpha1.ClusterNetworkPolicySpec{
+					AppliedTo: []crdv1alpha1.NetworkPolicyPeer{
+						{
+							PodSelector: &metav1.LabelSelector{
+								MatchLabels: map[string]string{"foo1": "bar1"},
+							},
+						},
+					},
+					Tier: "Application",
+					Egress: []crdv1alpha1.Rule{
+						{
+							Action: &passAction,
+							To: []crdv1alpha1.NetworkPolicyPeer{
+								{
+									PodSelector: &metav1.LabelSelector{
+										MatchLabels: map[string]string{"foo2": "bar2"},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			expectedReason: "",
+		},
+		{
+			name: "acnp-non-unique-rule-name",
+			policy: &crdv1alpha1.ClusterNetworkPolicy{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "acnp-non-unique-rule-name",
+				},
+				Spec: crdv1alpha1.ClusterNetworkPolicySpec{
+					AppliedTo: []crdv1alpha1.NetworkPolicyPeer{
+						{
+							PodSelector: &metav1.LabelSelector{
+								MatchLabels: map[string]string{"foo1": "bar1"},
+							},
+						},
+					},
+					Ingress: []crdv1alpha1.Rule{
+						{
+							Action: &allowAction,
+							From: []crdv1alpha1.NetworkPolicyPeer{
+								{
+									PodSelector: &metav1.LabelSelector{
+										MatchLabels: map[string]string{"foo2": "bar2"},
+									},
+								},
+							},
+							Name: "rule1",
+						},
+						{
+							Action: &passAction,
+							From: []crdv1alpha1.NetworkPolicyPeer{
+								{
+									PodSelector: &metav1.LabelSelector{
+										MatchLabels: map[string]string{"foo3": "bar3"},
+									},
+								},
+							},
+							Name: "rule1",
+						},
+					},
+				},
+			},
+			expectedReason: "rules names must be unique within the policy",
+		},
+		{
+			name: "acnp-appliedto-both-spec-rule",
+			policy: &crdv1alpha1.ClusterNetworkPolicy{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "acnp-appliedto-both-spec-rule",
+				},
+				Spec: crdv1alpha1.ClusterNetworkPolicySpec{
+					AppliedTo: []crdv1alpha1.NetworkPolicyPeer{
+						{
+							PodSelector: &metav1.LabelSelector{
+								MatchLabels: map[string]string{"foo1": "bar1"},
+							},
+						},
+					},
+					Ingress: []crdv1alpha1.Rule{
+						{
+							Action: &allowAction,
+							From: []crdv1alpha1.NetworkPolicyPeer{
+								{
+									PodSelector: &metav1.LabelSelector{
+										MatchLabels: map[string]string{"foo2": "bar2"},
+									},
+								},
+							},
+							AppliedTo: []crdv1alpha1.NetworkPolicyPeer{
+								{
+									PodSelector: &metav1.LabelSelector{
+										MatchLabels: map[string]string{"foo3": "bar3"},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			expectedReason: "appliedTo should not be set in both spec and rules",
+		},
+		{
+			name: "acnp-no-appliedto",
+			policy: &crdv1alpha1.ClusterNetworkPolicy{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "acnp-no-appliedto",
+				},
+				Spec: crdv1alpha1.ClusterNetworkPolicySpec{
+					Ingress: []crdv1alpha1.Rule{
+						{
+							Action: &allowAction,
+							From: []crdv1alpha1.NetworkPolicyPeer{
+								{
+									PodSelector: &metav1.LabelSelector{
+										MatchLabels: map[string]string{"foo2": "bar2"},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			expectedReason: "appliedTo needs to be set in either spec or rules",
+		},
+		{
+			name: "acnp-portion-rule-appliedto",
+			policy: &crdv1alpha1.ClusterNetworkPolicy{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "acnp-portion-rule-appliedto",
+				},
+				Spec: crdv1alpha1.ClusterNetworkPolicySpec{
+					Ingress: []crdv1alpha1.Rule{
+						{
+							Action: &allowAction,
+							From: []crdv1alpha1.NetworkPolicyPeer{
+								{
+									PodSelector: &metav1.LabelSelector{
+										MatchLabels: map[string]string{"foo1": "bar1"},
+									},
+								},
+							},
+							AppliedTo: []crdv1alpha1.NetworkPolicyPeer{
+								{
+									PodSelector: &metav1.LabelSelector{
+										MatchLabels: map[string]string{"foo2": "bar2"},
+									},
+								},
+							},
+							Name: "rule1",
+						},
+						{
+							Action: &passAction,
+							From: []crdv1alpha1.NetworkPolicyPeer{
+								{
+									PodSelector: &metav1.LabelSelector{
+										MatchLabels: map[string]string{"foo3": "bar3"},
+									},
+								},
+							},
+							Name: "rule2",
+						},
+					},
+				},
+			},
+			expectedReason: "appliedTo field should either be set in all rules or in none of them",
+		},
+		{
+			name: "acnp-rule-appliedto",
+			policy: &crdv1alpha1.ClusterNetworkPolicy{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "acnp-rule-appliedto",
+				},
+				Spec: crdv1alpha1.ClusterNetworkPolicySpec{
+					Ingress: []crdv1alpha1.Rule{
+						{
+							Action: &allowAction,
+							From: []crdv1alpha1.NetworkPolicyPeer{
+								{
+									PodSelector: &metav1.LabelSelector{
+										MatchLabels: map[string]string{"foo1": "bar1"},
+									},
+								},
+							},
+							AppliedTo: []crdv1alpha1.NetworkPolicyPeer{
+								{
+									PodSelector: &metav1.LabelSelector{
+										MatchLabels: map[string]string{"foo2": "bar2"},
+									},
+								},
+							},
+							Name: "rule1",
+						},
+						{
+							Action: &passAction,
+							From: []crdv1alpha1.NetworkPolicyPeer{
+								{
+									PodSelector: &metav1.LabelSelector{
+										MatchLabels: map[string]string{"foo3": "bar3"},
+									},
+								},
+							},
+							AppliedTo: []crdv1alpha1.NetworkPolicyPeer{
+								{
+									PodSelector: &metav1.LabelSelector{
+										MatchLabels: map[string]string{"foo4": "bar4"},
+									},
+								},
+							},
+							Name: "rule2",
+						},
+					},
+				},
+			},
+			expectedReason: "",
+		},
+		{
+			name: "acnp-appliedto-group-set-with-psel",
+			policy: &crdv1alpha1.ClusterNetworkPolicy{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "acnp-appliedto-group-set-with-psel",
+				},
+				Spec: crdv1alpha1.ClusterNetworkPolicySpec{
+					AppliedTo: []crdv1alpha1.NetworkPolicyPeer{
+						{
+							PodSelector: &metav1.LabelSelector{
+								MatchLabels: map[string]string{"foo1": "bar1"},
+							},
+							Group: "group1",
+						},
+					},
+				},
+			},
+			expectedReason: "group cannot be set with other peers in appliedTo",
+		},
+		{
+			name: "acnp-appliedto-group-set-with-nssel",
+			policy: &crdv1alpha1.ClusterNetworkPolicy{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "acnp-appliedto-group-set-with-nssel",
+				},
+				Spec: crdv1alpha1.ClusterNetworkPolicySpec{
+					AppliedTo: []crdv1alpha1.NetworkPolicyPeer{
+						{
+							NamespaceSelector: &metav1.LabelSelector{
+								MatchLabels: map[string]string{"foo1": "bar1"},
+							},
+							Group: "group1",
+						},
+					},
+				},
+			},
+			expectedReason: "group cannot be set with other peers in appliedTo",
+		},
+		{
+			name: "acnp-appliedto-group-alone",
+			policy: &crdv1alpha1.ClusterNetworkPolicy{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "acnp-appliedto-group-alone",
+				},
+				Spec: crdv1alpha1.ClusterNetworkPolicySpec{
+					AppliedTo: []crdv1alpha1.NetworkPolicyPeer{
+						{
+							Group: "group1",
+						},
+					},
+					Ingress: []crdv1alpha1.Rule{
+						{
+							Action: &allowAction,
+							From: []crdv1alpha1.NetworkPolicyPeer{
+								{
+									Namespaces: &crdv1alpha1.PeerNamespaces{
+										Match: crdv1alpha1.NamespaceMatchSelf,
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			expectedReason: "",
+		},
+		{
+			name: "acnp-rule-group-set-with-psel",
+			policy: &crdv1alpha1.ClusterNetworkPolicy{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "acnp-rule-group-set-with-psel",
+				},
+				Spec: crdv1alpha1.ClusterNetworkPolicySpec{
+					AppliedTo: []crdv1alpha1.NetworkPolicyPeer{
+						{
+							NamespaceSelector: &metav1.LabelSelector{
+								MatchLabels: map[string]string{"foo1": "bar1"},
+							},
+						},
+					},
+					Ingress: []crdv1alpha1.Rule{
+						{
+							Action: &allowAction,
+							From: []crdv1alpha1.NetworkPolicyPeer{
+								{
+									PodSelector: &metav1.LabelSelector{
+										MatchLabels: map[string]string{"foo2": "bar2"},
+									},
+									Group: "group1",
+								},
+							},
+						},
+					},
+				},
+			},
+			expectedReason: "group cannot be set with other peers in rules",
+		},
+		{
+			name: "acnp-rule-group-set-with-nssel",
+			policy: &crdv1alpha1.ClusterNetworkPolicy{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "acnp-rule-group-set-with-nssel",
+				},
+				Spec: crdv1alpha1.ClusterNetworkPolicySpec{
+					AppliedTo: []crdv1alpha1.NetworkPolicyPeer{
+						{
+							NamespaceSelector: &metav1.LabelSelector{
+								MatchLabels: map[string]string{"foo1": "bar1"},
+							},
+						},
+					},
+					Ingress: []crdv1alpha1.Rule{
+						{
+							Action: &allowAction,
+							From: []crdv1alpha1.NetworkPolicyPeer{
+								{
+									NamespaceSelector: &metav1.LabelSelector{
+										MatchLabels: map[string]string{"foo2": "bar2"},
+									},
+									Group: "group1",
+								},
+							},
+						},
+					},
+				},
+			},
+			expectedReason: "group cannot be set with other peers in rules",
+		},
+		{
+			name: "acnp-rule-group-set-with-ipblock",
+			policy: &crdv1alpha1.ClusterNetworkPolicy{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "acnp-rule-group-set-with-ipblock",
+				},
+				Spec: crdv1alpha1.ClusterNetworkPolicySpec{
+					AppliedTo: []crdv1alpha1.NetworkPolicyPeer{
+						{
+							NamespaceSelector: &metav1.LabelSelector{
+								MatchLabels: map[string]string{"foo1": "bar1"},
+							},
+						},
+					},
+					Ingress: []crdv1alpha1.Rule{
+						{
+							Action: &allowAction,
+							From: []crdv1alpha1.NetworkPolicyPeer{
+								{
+									IPBlock: &crdv1alpha1.IPBlock{
+										CIDR: "10.0.0.10/32",
+									},
+									Group: "group1",
+								},
+							},
+						},
+					},
+				},
+			},
+			expectedReason: "group cannot be set with other peers in rules",
+		},
+		{
+			name: "acnp-rule-group-set-with-ns",
+			policy: &crdv1alpha1.ClusterNetworkPolicy{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "acnp-rule-group-set-with-ns",
+				},
+				Spec: crdv1alpha1.ClusterNetworkPolicySpec{
+					AppliedTo: []crdv1alpha1.NetworkPolicyPeer{
+						{
+							NamespaceSelector: &metav1.LabelSelector{
+								MatchLabels: map[string]string{"foo1": "bar1"},
+							},
+						},
+					},
+					Ingress: []crdv1alpha1.Rule{
+						{
+							Action: &allowAction,
+							From: []crdv1alpha1.NetworkPolicyPeer{
+								{
+									Namespaces: &crdv1alpha1.PeerNamespaces{
+										Match: crdv1alpha1.NamespaceMatchSelf,
+									},
+									Group: "group1",
+								},
+							},
+						},
+					},
+				},
+			},
+			expectedReason: "group cannot be set with other peers in rules",
+		},
+		{
+			name: "acnp-rule-group-set-with-fqdn",
+			policy: &crdv1alpha1.ClusterNetworkPolicy{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "acnp-rule-group-set-with-fqdn",
+				},
+				Spec: crdv1alpha1.ClusterNetworkPolicySpec{
+					AppliedTo: []crdv1alpha1.NetworkPolicyPeer{
+						{
+							NamespaceSelector: &metav1.LabelSelector{
+								MatchLabels: map[string]string{"foo1": "bar1"},
+							},
+						},
+					},
+					Egress: []crdv1alpha1.Rule{
+						{
+							Action: &allowAction,
+							To: []crdv1alpha1.NetworkPolicyPeer{
+								{
+									FQDN:  "foo.bar",
+									Group: "group1",
+								},
+							},
+						},
+					},
+				},
+			},
+			expectedReason: "group cannot be set with other peers in rules",
+		},
+		{
+			name: "acnp-rule-group-set-with-eesel",
+			policy: &crdv1alpha1.ClusterNetworkPolicy{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "acnp-rule-group-set-with-eesel",
+				},
+				Spec: crdv1alpha1.ClusterNetworkPolicySpec{
+					AppliedTo: []crdv1alpha1.NetworkPolicyPeer{
+						{
+							NamespaceSelector: &metav1.LabelSelector{
+								MatchLabels: map[string]string{"foo1": "bar1"},
+							},
+						},
+					},
+					Ingress: []crdv1alpha1.Rule{
+						{
+							Action: &allowAction,
+							From: []crdv1alpha1.NetworkPolicyPeer{
+								{
+									ExternalEntitySelector: &metav1.LabelSelector{
+										MatchLabels: map[string]string{"foo2": "bar2"},
+									},
+									Group: "group1",
+								},
+							},
+						},
+					},
+				},
+			},
+			expectedReason: "group cannot be set with other peers in rules",
+		},
+		{
+			name: "acnp-rule-group-alone",
+			policy: &crdv1alpha1.ClusterNetworkPolicy{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "acnp-rule-group-alone",
+				},
+				Spec: crdv1alpha1.ClusterNetworkPolicySpec{
+					AppliedTo: []crdv1alpha1.NetworkPolicyPeer{
+						{
+							NamespaceSelector: &metav1.LabelSelector{
+								MatchLabels: map[string]string{"foo1": "bar1"},
+							},
+						},
+					},
+					Ingress: []crdv1alpha1.Rule{
+						{
+							Action: &allowAction,
+							From: []crdv1alpha1.NetworkPolicyPeer{
+								{
+									Group: "group1",
+								},
+							},
+						},
+					},
+				},
+			},
+			expectedReason: "",
+		},
+		{
+			name: "acnp-rule-ns-set-with-nssel",
+			policy: &crdv1alpha1.ClusterNetworkPolicy{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "acnp-rule-ns-set-with-nssel",
+				},
+				Spec: crdv1alpha1.ClusterNetworkPolicySpec{
+					AppliedTo: []crdv1alpha1.NetworkPolicyPeer{
+						{
+							NamespaceSelector: &metav1.LabelSelector{
+								MatchLabels: map[string]string{"foo1": "bar1"},
+							},
+						},
+					},
+					Ingress: []crdv1alpha1.Rule{
+						{
+							Action: &allowAction,
+							From: []crdv1alpha1.NetworkPolicyPeer{
+								{
+									NamespaceSelector: &metav1.LabelSelector{
+										MatchLabels: map[string]string{"foo2": "bar2"},
+									},
+									Namespaces: &crdv1alpha1.PeerNamespaces{
+										Match: crdv1alpha1.NamespaceMatchSelf,
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			expectedReason: "namespaces and namespaceSelector cannot be set at the same time for a single NetworkPolicyPeer",
+		},
+		{
+			name: "acnp-toservice-set-with-to",
+			policy: &crdv1alpha1.ClusterNetworkPolicy{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "acnp-toservice-set-with-to",
+				},
+				Spec: crdv1alpha1.ClusterNetworkPolicySpec{
+					AppliedTo: []crdv1alpha1.NetworkPolicyPeer{
+						{
+							NamespaceSelector: &metav1.LabelSelector{
+								MatchLabels: map[string]string{"foo1": "bar1"},
+							},
+						},
+					},
+					Egress: []crdv1alpha1.Rule{
+						{
+							Action: &allowAction,
+							To: []crdv1alpha1.NetworkPolicyPeer{
+								{
+									NamespaceSelector: &metav1.LabelSelector{
+										MatchLabels: map[string]string{"foo2": "bar2"},
+									},
+								},
+							},
+							ToServices: []crdv1alpha1.ServiceReference{
+								{
+									Name:      "foo",
+									Namespace: "bar",
+								},
+							},
+						},
+					},
+				},
+			},
+			expectedReason: "`toServices` can't be used with `to` or `ports`",
+		},
+		{
+			name: "acnp-toservice-set-with-ports",
+			policy: &crdv1alpha1.ClusterNetworkPolicy{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "acnp-toservice-set-with-ports",
+				},
+				Spec: crdv1alpha1.ClusterNetworkPolicySpec{
+					AppliedTo: []crdv1alpha1.NetworkPolicyPeer{
+						{
+							NamespaceSelector: &metav1.LabelSelector{
+								MatchLabels: map[string]string{"foo1": "bar1"},
+							},
+						},
+					},
+					Egress: []crdv1alpha1.Rule{
+						{
+							Action: &allowAction,
+							Ports: []crdv1alpha1.NetworkPolicyPort{
+								{
+									Port: &int80,
+								},
+							},
+							ToServices: []crdv1alpha1.ServiceReference{
+								{
+									Name:      "foo",
+									Namespace: "bar",
+								},
+							},
+						},
+					},
+				},
+			},
+			expectedReason: "`toServices` can't be used with `to` or `ports`",
+		},
+		{
+			name: "acnp-toservice-alone",
+			policy: &crdv1alpha1.ClusterNetworkPolicy{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "acnp-toservice-alone",
+				},
+				Spec: crdv1alpha1.ClusterNetworkPolicySpec{
+					AppliedTo: []crdv1alpha1.NetworkPolicyPeer{
+						{
+							NamespaceSelector: &metav1.LabelSelector{
+								MatchLabels: map[string]string{"foo1": "bar1"},
+							},
+						},
+					},
+					Egress: []crdv1alpha1.Rule{
+						{
+							Action: &allowAction,
+							ToServices: []crdv1alpha1.ServiceReference{
+								{
+									Name:      "foo",
+									Namespace: "bar",
+								},
+							},
+						},
+					},
+				},
+			},
+			expectedReason: "",
+		},
+		{
+			name: "acnp-invalid-fqdn",
+			policy: &crdv1alpha1.ClusterNetworkPolicy{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "acnp-invalid-fqdn",
+				},
+				Spec: crdv1alpha1.ClusterNetworkPolicySpec{
+					AppliedTo: []crdv1alpha1.NetworkPolicyPeer{
+						{
+							NamespaceSelector: &metav1.LabelSelector{
+								MatchLabels: map[string]string{"foo1": "bar1"},
+							},
+						},
+					},
+					Egress: []crdv1alpha1.Rule{
+						{
+							Action: &allowAction,
+							To: []crdv1alpha1.NetworkPolicyPeer{
+								{
+									FQDN: "foo!bar",
+								},
+							},
+						},
+					},
+				},
+			},
+			expectedReason: "invalid characters in egress rule fqdn field: foo!bar",
+		},
+		{
+			name: "acnp-valid-fqdn",
+			policy: &crdv1alpha1.ClusterNetworkPolicy{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "acnp-valid-fqdn",
+				},
+				Spec: crdv1alpha1.ClusterNetworkPolicySpec{
+					AppliedTo: []crdv1alpha1.NetworkPolicyPeer{
+						{
+							NamespaceSelector: &metav1.LabelSelector{
+								MatchLabels: map[string]string{"foo1": "bar1"},
+							},
+						},
+					},
+					Egress: []crdv1alpha1.Rule{
+						{
+							Action: &allowAction,
+							To: []crdv1alpha1.NetworkPolicyPeer{
+								{
+									FQDN: "foo.bar",
+								},
+							},
+						},
+					},
+				},
+			},
+			expectedReason: "",
+		},
+		{
+			name: "acnp-endport-without-port",
+			policy: &crdv1alpha1.ClusterNetworkPolicy{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "acnp-endport-without-port",
+				},
+				Spec: crdv1alpha1.ClusterNetworkPolicySpec{
+					AppliedTo: []crdv1alpha1.NetworkPolicyPeer{
+						{
+							NamespaceSelector: &metav1.LabelSelector{
+								MatchLabels: map[string]string{"foo1": "bar1"},
+							},
+						},
+					},
+					Ingress: []crdv1alpha1.Rule{
+						{
+							Action: &allowAction,
+							Ports: []crdv1alpha1.NetworkPolicyPort{
+								{
+									EndPort: &int32For80,
+								},
+							},
+						},
+					},
+				},
+			},
+			expectedReason: "if `endPort` is specified `port` must be specified",
+		},
+		{
+			name: "acnp-endport-smaller-port",
+			policy: &crdv1alpha1.ClusterNetworkPolicy{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "acnp-endport-smaller-port",
+				},
+				Spec: crdv1alpha1.ClusterNetworkPolicySpec{
+					AppliedTo: []crdv1alpha1.NetworkPolicyPeer{
+						{
+							NamespaceSelector: &metav1.LabelSelector{
+								MatchLabels: map[string]string{"foo1": "bar1"},
+							},
+						},
+					},
+					Ingress: []crdv1alpha1.Rule{
+						{
+							Action: &allowAction,
+							Ports: []crdv1alpha1.NetworkPolicyPort{
+								{
+									Port:    &int81,
+									EndPort: &int32For80,
+								},
+							},
+						},
+					},
+				},
+			},
+			expectedReason: "`endPort` should be greater than or equal to `port`",
+		},
+		{
+			name: "acnp-named-port-with-endport",
+			policy: &crdv1alpha1.ClusterNetworkPolicy{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "acnp-named-port-with-endport",
+				},
+				Spec: crdv1alpha1.ClusterNetworkPolicySpec{
+					AppliedTo: []crdv1alpha1.NetworkPolicyPeer{
+						{
+							NamespaceSelector: &metav1.LabelSelector{
+								MatchLabels: map[string]string{"foo1": "bar1"},
+							},
+						},
+					},
+					Ingress: []crdv1alpha1.Rule{
+						{
+							Action: &allowAction,
+							Ports: []crdv1alpha1.NetworkPolicyPort{
+								{
+									Port:    &strHTTP,
+									EndPort: &int32For80,
+								},
+							},
+						},
+					},
+				},
+			},
+			expectedReason: "if `port` is a string `endPort` cannot be specified",
+		},
+		{
+			name: "acnp-port-range",
+			policy: &crdv1alpha1.ClusterNetworkPolicy{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "acnp-port-range",
+				},
+				Spec: crdv1alpha1.ClusterNetworkPolicySpec{
+					AppliedTo: []crdv1alpha1.NetworkPolicyPeer{
+						{
+							NamespaceSelector: &metav1.LabelSelector{
+								MatchLabels: map[string]string{"foo1": "bar1"},
+							},
+						},
+					},
+					Ingress: []crdv1alpha1.Rule{
+						{
+							Action: &allowAction,
+							Ports: []crdv1alpha1.NetworkPolicyPort{
+								{
+									Port:    &int80,
+									EndPort: &int32For1999,
+								},
+							},
+						},
+					},
+				},
+			},
+			expectedReason: "",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			_, c := newController()
+			v := NewNetworkPolicyValidator(c.NetworkPolicyController)
+			actualReason, allowed := v.validateAntreaPolicy(tt.policy, 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/test/e2e/antreapolicy_test.go b/test/e2e/antreapolicy_test.go
index 13b46cde8..ff10af7d3 100644
--- a/test/e2e/antreapolicy_test.go
+++ b/test/e2e/antreapolicy_test.go
@@ -323,293 +323,6 @@ func testInvalidACNPNoPriority(t *testing.T) {
 	}
 }
 
-func testInvalidACNPRuleNameNotUnique(t *testing.T) {
-	invalidNpErr := fmt.Errorf("invalid Antrea ClusterNetworkPolicy without unique rule names accepted")
-	builder := &ClusterNetworkPolicySpecBuilder{}
-	builder = builder.SetName("acnp-rule-name-not-unique").
-		SetAppliedToGroup([]ACNPAppliedToSpec{{PodSelector: map[string]string{"pod": "a"}}}).
-		AddIngress(v1.ProtocolTCP, &p80, nil, nil, nil, map[string]string{"pod": "b"}, map[string]string{"ns": "x"},
-			nil, nil, false, nil, crdv1alpha1.RuleActionAllow, "", "not-unique").
-		AddIngress(v1.ProtocolTCP, &p81, nil, nil, nil, map[string]string{"pod": "c"}, map[string]string{"ns": "x"},
-			nil, nil, false, nil, crdv1alpha1.RuleActionAllow, "", "not-unique")
-	acnp := builder.Get()
-	log.Debugf("creating ACNP %v", acnp.Name)
-	if _, err := k8sUtils.CreateOrUpdateACNP(acnp); err == nil {
-		// Above creation of ACNP must fail as it is an invalid spec.
-		failOnError(invalidNpErr, t)
-	}
-}
-
-func testInvalidACNPTierDoesNotExist(t *testing.T) {
-	invalidNpErr := fmt.Errorf("invalid Antrea ClusterNetworkPolicy without existing Tier accepted")
-	builder := &ClusterNetworkPolicySpecBuilder{}
-	builder = builder.SetName("acnp-tier-not-exist").
-		SetAppliedToGroup([]ACNPAppliedToSpec{{PodSelector: map[string]string{"pod": "a"}}}).
-		SetTier("i-dont-exist")
-	acnp := builder.Get()
-	log.Debugf("creating ACNP %v", acnp.Name)
-	if _, err := k8sUtils.CreateOrUpdateACNP(acnp); err == nil {
-		// Above creation of ACNP must fail as it is an invalid spec.
-		failOnError(invalidNpErr, t)
-	}
-}
-
-func testInvalidACNPPortRangePortUnset(t *testing.T) {
-	invalidNpErr := fmt.Errorf("invalid Antrea ClusterNetworkPolicy egress rule with endPort but no port accepted")
-	builder := &ClusterNetworkPolicySpecBuilder{}
-	builder = builder.SetName("acnp-egress-port-range-port-unset").
-		SetPriority(1.0).
-		SetAppliedToGroup([]ACNPAppliedToSpec{{PodSelector: map[string]string{"pod": "b"}}})
-	builder.AddEgress(v1.ProtocolTCP, nil, nil, &p8085, nil, map[string]string{"pod": "c"}, map[string]string{"ns": "x"},
-		nil, nil, false, nil, crdv1alpha1.RuleActionDrop, "", "acnp-port-range")
-
-	acnp := builder.Get()
-	log.Debugf("creating ACNP %v", acnp.Name)
-	if _, err := k8sUtils.CreateOrUpdateACNP(acnp); err == nil {
-		// Above creation of ACNP must fail as it is an invalid spec.
-		failOnError(invalidNpErr, t)
-	}
-}
-
-func testInvalidACNPPortRangeEndPortSmall(t *testing.T) {
-	invalidNpErr := fmt.Errorf("invalid Antrea ClusterNetworkPolicy egress rule with endPort smaller than port accepted")
-	builder := &ClusterNetworkPolicySpecBuilder{}
-	builder = builder.SetName("acnp-egress-port-range-endport-small").
-		SetPriority(1.0).
-		SetAppliedToGroup([]ACNPAppliedToSpec{{PodSelector: map[string]string{"pod": "b"}}})
-	builder.AddEgress(v1.ProtocolTCP, &p8082, nil, &p8081, nil, map[string]string{"pod": "c"}, map[string]string{"ns": "x"},
-		nil, nil, false, nil, crdv1alpha1.RuleActionDrop, "", "acnp-port-range")
-
-	acnp := builder.Get()
-	log.Debugf("creating ACNP %v", acnp.Name)
-	if _, err := k8sUtils.CreateOrUpdateACNP(acnp); err == nil {
-		// Above creation of ACNP must fail as it is an invalid spec.
-		failOnError(invalidNpErr, t)
-	}
-}
-
-func testInvalidACNPSpecAppliedToRuleAppliedToSet(t *testing.T) {
-	invalidNpErr := fmt.Errorf("invalid Antrea ClusterNetworkPolicy with spec appliedTo and rules appliedTo set")
-	ruleAppTo := ACNPAppliedToSpec{
-		PodSelector: map[string]string{"pod": "b"},
-	}
-	builder := &ClusterNetworkPolicySpecBuilder{}
-	builder = builder.SetName("acnp-spec-appto-and-rules-appto").
-		SetPriority(1.0).
-		SetAppliedToGroup([]ACNPAppliedToSpec{{PodSelector: map[string]string{"pod": "b"}}})
-	builder = builder.AddIngress(v1.ProtocolTCP, &p80, nil, nil, nil, map[string]string{"pod": "b"}, map[string]string{"ns": "x"},
-		nil, nil, false, []ACNPAppliedToSpec{ruleAppTo}, crdv1alpha1.RuleActionAllow, "", "")
-	acnp := builder.Get()
-	log.Debugf("creating ACNP %v", acnp.Name)
-	if _, err := k8sUtils.CreateOrUpdateACNP(acnp); err == nil {
-		// Above creation of ACNP must fail as it is an invalid spec.
-		failOnError(invalidNpErr, t)
-	}
-}
-
-func testInvalidACNPAppliedToNotSetInAllRules(t *testing.T) {
-	invalidNpErr := fmt.Errorf("invalid Antrea ClusterNetworkPolicy with appliedTo not set in all rules")
-	ruleAppTo := ACNPAppliedToSpec{
-		PodSelector: map[string]string{"pod": "b"},
-	}
-	builder := &ClusterNetworkPolicySpecBuilder{}
-	builder = builder.SetName("acnp-appto-not-set-in-all-rules").
-		SetPriority(1.0)
-	builder = builder.AddIngress(v1.ProtocolTCP, &p80, nil, nil, nil, map[string]string{"pod": "b"}, map[string]string{"ns": "x"},
-		nil, nil, false, []ACNPAppliedToSpec{ruleAppTo}, crdv1alpha1.RuleActionAllow, "", "").
-		AddIngress(v1.ProtocolTCP, &p81, nil, nil, nil, map[string]string{"pod": "c"}, map[string]string{"ns": "x"},
-			nil, nil, false, nil, crdv1alpha1.RuleActionAllow, "", "")
-	acnp := builder.Get()
-	log.Debugf("creating ACNP %v", acnp.Name)
-	if _, err := k8sUtils.CreateOrUpdateACNP(acnp); err == nil {
-		// Above creation of ACNP must fail as it is an invalid spec.
-		failOnError(invalidNpErr, t)
-	}
-}
-
-func testInvalidACNPAppliedToGroupSetWithPodSelector(t *testing.T) {
-	invalidNpErr := fmt.Errorf("invalid Antrea ClusterNetworkPolicy with appliedTo set group with podSelector")
-	appTo := ACNPAppliedToSpec{
-		PodSelector: map[string]string{"pod": "b"},
-		Group:       "cgA",
-	}
-	builder := &ClusterNetworkPolicySpecBuilder{}
-	builder = builder.SetName("acnp-appto-set-group-with-psel").
-		SetPriority(1.0).
-		SetAppliedToGroup([]ACNPAppliedToSpec{appTo})
-	builder = builder.AddIngress(v1.ProtocolTCP, &p80, nil, nil, nil, map[string]string{"pod": "b"}, map[string]string{"ns": "x"},
-		nil, nil, false, nil, crdv1alpha1.RuleActionAllow, "", "")
-	acnp := builder.Get()
-	log.Debugf("creating ACNP %v", acnp.Name)
-	if _, err := k8sUtils.CreateOrUpdateACNP(acnp); err == nil {
-		// Above creation of ACNP must fail as it is an invalid spec.
-		failOnError(invalidNpErr, t)
-	}
-}
-
-func testInvalidACNPAppliedToGroupSetWithNSSelector(t *testing.T) {
-	invalidNpErr := fmt.Errorf("invalid Antrea ClusterNetworkPolicy with appliedTo set group with nsSelector")
-	appTo := ACNPAppliedToSpec{
-		NSSelector: map[string]string{"ns": "x"},
-		Group:      "cgA",
-	}
-	builder := &ClusterNetworkPolicySpecBuilder{}
-	builder = builder.SetName("acnp-appto-set-group-with-nssel").
-		SetPriority(1.0).
-		SetAppliedToGroup([]ACNPAppliedToSpec{appTo})
-	builder = builder.AddIngress(v1.ProtocolTCP, &p80, nil, nil, nil, map[string]string{"pod": "b"}, map[string]string{"ns": "x"},
-		nil, nil, false, nil, crdv1alpha1.RuleActionAllow, "", "")
-	acnp := builder.Get()
-	log.Debugf("creating ACNP %v", acnp.Name)
-	if _, err := k8sUtils.CreateOrUpdateACNP(acnp); err == nil {
-		// Above creation of ACNP must fail as it is an invalid spec.
-		failOnError(invalidNpErr, t)
-	}
-}
-
-func testInvalidACNPIngressPeerCGSetWithPodSelector(t *testing.T) {
-	cgA := "cgA"
-	selectorA := metav1.LabelSelector{MatchLabels: map[string]string{"foo1": "bar1"}}
-	ruleAppTo := ACNPAppliedToSpec{
-		PodSelector: map[string]string{"pod": "b"},
-	}
-	k8sUtils.CreateCG(cgA, &selectorA, nil, nil)
-	invalidNpErr := fmt.Errorf("invalid Antrea ClusterNetworkPolicy with group and podSelector in NetworkPolicyPeer set")
-	builder := &ClusterNetworkPolicySpecBuilder{}
-	builder = builder.SetName("acnp-ingress-group-podselector-set").
-		SetPriority(1.0)
-	builder = builder.AddIngress(v1.ProtocolTCP, &p80, nil, nil, nil, map[string]string{"pod": "b"}, nil,
-		nil, nil, false, []ACNPAppliedToSpec{ruleAppTo}, crdv1alpha1.RuleActionAllow, "cgA", "")
-	acnp := builder.Get()
-	log.Debugf("creating ACNP %v", acnp.Name)
-	if _, err := k8sUtils.CreateOrUpdateACNP(acnp); err == nil {
-		// Above creation of ACNP must fail as it is an invalid spec.
-		failOnError(invalidNpErr, t)
-	}
-	failOnError(k8sUtils.CleanCGs(), t)
-}
-
-func testInvalidACNPIngressPeerCGSetWithNSSelector(t *testing.T) {
-	cgA := "cgA"
-	selectorA := metav1.LabelSelector{MatchLabels: map[string]string{"foo1": "bar1"}}
-	k8sUtils.CreateCG(cgA, &selectorA, nil, nil)
-	invalidNpErr := fmt.Errorf("invalid Antrea ClusterNetworkPolicy with group and namespaceSelector in NetworkPolicyPeer set")
-	builder := &ClusterNetworkPolicySpecBuilder{}
-	builder = builder.SetName("acnp-ingress-group-nsselector-set").
-		SetPriority(1.0).
-		SetAppliedToGroup([]ACNPAppliedToSpec{{PodSelector: map[string]string{"pod": "b"}}})
-	builder = builder.AddIngress(v1.ProtocolTCP, &p80, nil, nil, nil, nil, map[string]string{"ns": "x"},
-		nil, nil, false, nil, crdv1alpha1.RuleActionAllow, "cgA", "")
-	acnp := builder.Get()
-	log.Debugf("creating ACNP %v", acnp.Name)
-	if _, err := k8sUtils.CreateOrUpdateACNP(acnp); err == nil {
-		// Above creation of ACNP must fail as it is an invalid spec.
-		failOnError(invalidNpErr, t)
-	}
-	failOnError(k8sUtils.CleanCGs(), t)
-}
-
-func testInvalidACNPIngressPeerCGSetWithIPBlock(t *testing.T) {
-	cgA := "cgA"
-	selectorA := metav1.LabelSelector{MatchLabels: map[string]string{"foo1": "bar1"}}
-	k8sUtils.CreateCG(cgA, &selectorA, nil, nil)
-	invalidNpErr := fmt.Errorf("invalid Antrea ClusterNetworkPolicy with group and ipBlock in NetworkPolicyPeer set")
-	cidr := "10.0.0.10/32"
-	builder := &ClusterNetworkPolicySpecBuilder{}
-	builder = builder.SetName("acnp-ingress-group-ipblock-set").
-		SetPriority(1.0).
-		SetAppliedToGroup([]ACNPAppliedToSpec{{Group: "cgA"}})
-	builder = builder.AddIngress(v1.ProtocolTCP, &p80, nil, nil, &cidr, map[string]string{"pod": "b"}, map[string]string{"ns": "x"},
-		nil, nil, false, []ACNPAppliedToSpec{{Group: "cgB"}}, crdv1alpha1.RuleActionAllow, "", "")
-	acnp := builder.Get()
-	log.Debugf("creating ACNP %v", acnp.Name)
-	if _, err := k8sUtils.CreateOrUpdateACNP(acnp); err == nil {
-		// Above creation of ACNP must fail as it is an invalid spec.
-		failOnError(invalidNpErr, t)
-	}
-}
-
-func testInvalidACNPEgressPeerCGSetWithFQDN(t *testing.T) {
-	cgA := "cgA"
-	selectorA := metav1.LabelSelector{MatchLabels: map[string]string{"foo1": "bar1"}}
-	k8sUtils.CreateCG(cgA, &selectorA, nil, nil)
-	invalidNpErr := fmt.Errorf("invalid Antrea ClusterNetworkPolicy with group and fqdn in NetworkPolicyPeer set")
-	builder := &ClusterNetworkPolicySpecBuilder{}
-	builder = builder.SetName("acnp-egress-group-fqdn-set").
-		SetPriority(1.0).
-		SetAppliedToGroup([]ACNPAppliedToSpec{{PodSelector: map[string]string{"pod": "b"}}})
-	builder = builder.AddEgress(v1.ProtocolTCP, &p80, nil, nil, nil, nil, nil,
-		nil, nil, false, nil, crdv1alpha1.RuleActionAllow, "cgA", "")
-	builder.Spec.Egress[0].To[0].FQDN = "google.com"
-
-	acnp := builder.Get()
-	log.Debugf("creating ACNP %v", acnp.Name)
-	if _, err := k8sUtils.CreateOrUpdateACNP(acnp); err == nil {
-		// Above creation of ACNP must fail as it is an invalid spec.
-		failOnError(invalidNpErr, t)
-	}
-	failOnError(k8sUtils.CleanCGs(), t)
-}
-
-func testInvalidACNPIngressPeerCGSetWithNamespace(t *testing.T) {
-	cgA := "cgA"
-	selectorA := metav1.LabelSelector{MatchLabels: map[string]string{"foo1": "bar1"}}
-	k8sUtils.CreateCG(cgA, &selectorA, nil, nil)
-	invalidNpErr := fmt.Errorf("invalid Antrea ClusterNetworkPolicy with group and namespace in NetworkPolicyPeer set")
-	builder := &ClusterNetworkPolicySpecBuilder{}
-	builder = builder.SetName("acnp-ingress-group-ns-set").
-		SetPriority(1.0).
-		SetAppliedToGroup([]ACNPAppliedToSpec{{PodSelector: map[string]string{"pod": "b"}}})
-	builder = builder.AddIngress(v1.ProtocolTCP, &p80, nil, nil, nil, nil, nil,
-		nil, nil, true, nil, crdv1alpha1.RuleActionAllow, "cgA", "")
-	acnp := builder.Get()
-	log.Debugf("creating ACNP %v", acnp.Name)
-	if _, err := k8sUtils.CreateOrUpdateACNP(acnp); err == nil {
-		// Above creation of ACNP must fail as it is an invalid spec.
-		failOnError(invalidNpErr, t)
-	}
-	failOnError(k8sUtils.CleanCGs(), t)
-}
-
-func testInvalidACNPIngressPeerCGSetWithEESelector(t *testing.T) {
-	cgA := "cgA"
-	selectorA := metav1.LabelSelector{MatchLabels: map[string]string{"foo1": "bar1"}}
-	k8sUtils.CreateCG(cgA, &selectorA, nil, nil)
-	invalidNpErr := fmt.Errorf("invalid Antrea ClusterNetworkPolicy with group and external entity selector in NetworkPolicyPeer set")
-	builder := &ClusterNetworkPolicySpecBuilder{}
-	builder = builder.SetName("acnp-ingress-group-eeselector-set").
-		SetPriority(1.0).
-		SetAppliedToGroup([]ACNPAppliedToSpec{{PodSelector: map[string]string{"pod": "b"}}})
-	builder = builder.AddIngress(v1.ProtocolTCP, &p80, nil, nil, nil, nil, nil,
-		nil, nil, true, nil, crdv1alpha1.RuleActionAllow, "cgA", "")
-	builder.Spec.Ingress[0].From[0].ExternalEntitySelector = &metav1.LabelSelector{
-		MatchLabels: map[string]string{"foo2": "bar2"},
-	}
-	acnp := builder.Get()
-	log.Debugf("creating ACNP %v", acnp.Name)
-	if _, err := k8sUtils.CreateOrUpdateACNP(acnp); err == nil {
-		// Above creation of ACNP must fail as it is an invalid spec.
-		failOnError(invalidNpErr, t)
-	}
-	failOnError(k8sUtils.CleanCGs(), t)
-}
-
-func testInvalidACNPIngressPeerNamespacesSetWithNSSelector(t *testing.T) {
-	invalidNpErr := fmt.Errorf("invalid Antrea ClusterNetworkPolicy with namespaces and namespaceSelector in NetworkPolicyPeer set")
-	builder := &ClusterNetworkPolicySpecBuilder{}
-	builder = builder.SetName("acnp-ingress-namespaces-nsselector-set").
-		SetPriority(1.0).
-		SetAppliedToGroup([]ACNPAppliedToSpec{{PodSelector: map[string]string{"pod": "b"}}})
-	builder = builder.AddIngress(v1.ProtocolTCP, &p80, nil, nil, nil, nil, map[string]string{"ns": "x"},
-		nil, nil, true, nil, crdv1alpha1.RuleActionAllow, "", "")
-	acnp := builder.Get()
-	log.Debugf("creating ACNP %v", acnp.Name)
-	if _, err := k8sUtils.CreateOrUpdateACNP(acnp); err == nil {
-		// Above creation of ACNP must fail as it is an invalid spec.
-		failOnError(invalidNpErr, t)
-	}
-}
-
 func testInvalidANPNoPriority(t *testing.T) {
 	invalidNpErr := fmt.Errorf("invalid Antrea NetworkPolicy without a priority accepted")
 	builder := &AntreaNetworkPolicySpecBuilder{}
@@ -3087,21 +2800,6 @@ func TestAntreaPolicy(t *testing.T) {
 
 	t.Run("TestGroupValidateAntreaNativePolicies", func(t *testing.T) {
 		t.Run("Case=ACNPNoPriority", func(t *testing.T) { testInvalidACNPNoPriority(t) })
-		t.Run("Case=ACNPRuleNameNotUniqueDenied", func(t *testing.T) { testInvalidACNPRuleNameNotUnique(t) })
-		t.Run("Case=ACNPTierDoesNotExistDenied", func(t *testing.T) { testInvalidACNPTierDoesNotExist(t) })
-		t.Run("Case=ACNPPortRangePortUnsetDenied", func(t *testing.T) { testInvalidACNPPortRangePortUnset(t) })
-		t.Run("Case=ACNPPortRangePortEndPortSmallDenied", func(t *testing.T) { testInvalidACNPPortRangeEndPortSmall(t) })
-		t.Run("Case=ACNPIngressPeerCGSetWithIPBlock", func(t *testing.T) { testInvalidACNPIngressPeerCGSetWithIPBlock(t) })
-		t.Run("Case=ACNPIngressPeerCGSetWithPodSelector", func(t *testing.T) { testInvalidACNPIngressPeerCGSetWithPodSelector(t) })
-		t.Run("Case=ACNPIngressPeerCGSetWithNSSelector", func(t *testing.T) { testInvalidACNPIngressPeerCGSetWithNSSelector(t) })
-		t.Run("Case=ACNPEgressPeerCGSetWithFQDN", func(t *testing.T) { testInvalidACNPEgressPeerCGSetWithFQDN(t) })
-		t.Run("Case=ACNPIngressPeerCGSetWithNamespace", func(t *testing.T) { testInvalidACNPIngressPeerCGSetWithNamespace(t) })
-		t.Run("Case=ACNPIngressPeerCGSetWithEESelector", func(t *testing.T) { testInvalidACNPIngressPeerCGSetWithEESelector(t) })
-		t.Run("Case=ACNPIngressPeerNamespaceSetWithNSSelector", func(t *testing.T) { testInvalidACNPIngressPeerNamespacesSetWithNSSelector(t) })
-		t.Run("Case=ACNPSpecAppliedToRuleAppliedToSet", func(t *testing.T) { testInvalidACNPSpecAppliedToRuleAppliedToSet(t) })
-		t.Run("Case=ACNPAppliedToNotSetInAllRules", func(t *testing.T) { testInvalidACNPAppliedToNotSetInAllRules(t) })
-		t.Run("Case=ACNPAppliedToGroupSetWithPodSelector", func(t *testing.T) { testInvalidACNPAppliedToGroupSetWithPodSelector(t) })
-		t.Run("Case=ACNPAppliedToGroupSetWithNSSelector", func(t *testing.T) { testInvalidACNPAppliedToGroupSetWithNSSelector(t) })
 		t.Run("Case=ANPNoPriority", func(t *testing.T) { testInvalidANPNoPriority(t) })
 		t.Run("Case=ANPRuleNameNotUniqueDenied", func(t *testing.T) { testInvalidANPRuleNameNotUnique(t) })
 		t.Run("Case=ANPTierDoesNotExistDenied", func(t *testing.T) { testInvalidANPTierDoesNotExist(t) })
diff --git a/test/e2e/framework.go b/test/e2e/framework.go
index 9c52d320a..7df6b4fe4 100644
--- a/test/e2e/framework.go
+++ b/test/e2e/framework.go
@@ -119,7 +119,7 @@ const (
 	busyboxImage        = "projects.registry.vmware.com/library/busybox"
 	mcjoinImage         = "projects.registry.vmware.com/antrea/mcjoin:v2.9"
 	netshootImage       = "projects.registry.vmware.com/antrea/netshoot:v0.1"
-	nginxImage          = "projects.registry.vmware.com/antrea/nginx"
+	nginxImage          = "projects.registry.vmware.com/antrea/nginx:1.21.6-alpine"
 	perftoolImage       = "projects.registry.vmware.com/antrea/perftool"
 	ipfixCollectorImage = "projects.registry.vmware.com/antrea/ipfix-collector:v0.5.11"
 	ipfixCollectorPort  = "4739"