Repository: spidernet-io/spiderpool
Pull request #1738 (2023-04-28 04:16:17)

docs: Update docs concepts/annotation.md
Signed-off-by: iiiceoo <ziqian.xue@daocloud.io>


Patch:
diff --git a/.github/workflows/call-release-image.yaml b/.github/workflows/call-release-image.yaml
index fb7eedf73..035c51230 100644
--- a/.github/workflows/call-release-image.yaml
+++ b/.github/workflows/call-release-image.yaml
@@ -74,6 +74,7 @@ jobs:
           make lint_dockerfile_trivy
 
       - name: Install Cosign
+        if: ${{ env.RUN_PUSH == 'true' }}
         uses: sigstore/cosign-installer@main
         with:
           cosign-release: 'v2.0.1' #do not upgrade, or else go error
@@ -108,7 +109,7 @@ jobs:
           COSIGN_EXPERIMENTAL: "true"
         run: |
           DIGEST=${{ steps.docker_build_release.outputs.digest }}
-          cosign sign -y ${{ env.ONLINE_REGISTER }}/${{ github.repository }}/${{ matrix.name }}${{ steps.ref.outputs.suffix }}@${DIGEST}
+          cosign sign -y ${{ env.ONLINE_REGISTER }}/${{ github.repository }}-${{ matrix.name }}${{ steps.ref.outputs.suffix }}@${DIGEST}
 
       - name: Generate SBOM
         shell: bash
@@ -118,18 +119,18 @@ jobs:
           #chmod +x  ./bom
           DIGEST=${{ steps.docker_build_release.outputs.digest }}
           #./bom generate -o ${{ matrix.name }}_${DIGEST}.spdx \
-          #      --image=${{ env.ONLINE_REGISTER }}/${{ github.repository }}/${{ matrix.name }}${{ steps.ref.outputs.suffix }}:${{ env.RUN_IMAGE_TAG }}@${DIGEST}
+          #      --image=${{ env.ONLINE_REGISTER }}/${{ github.repository }}-${{ matrix.name }}${{ steps.ref.outputs.suffix }}:${{ env.RUN_IMAGE_TAG }}@${DIGEST}
           wget https://github.com/anchore/syft/releases/download/v0.77.0/syft_0.77.0_linux_amd64.tar.gz
           tar xzvf syft_0.77.0_linux_amd64.tar.gz
           chmod +x syft
-          ./syft -o spdx ${{ env.ONLINE_REGISTER }}/${{ github.repository }}/${{ matrix.name }}${{ steps.ref.outputs.suffix }}@${DIGEST} > ${{ matrix.name }}_${DIGEST}.spdx
+          ./syft -o spdx ${{ env.ONLINE_REGISTER }}/${{ github.repository }}-${{ matrix.name }}${{ steps.ref.outputs.suffix }}@${DIGEST} > ${{ matrix.name }}_${DIGEST}.spdx
 
       - name: Attach SBOM to Container Images
         run: |
           set -x
           DIGEST=${{ steps.docker_build_release.outputs.digest }}
           cosign attach sbom --sbom ${{ matrix.name }}_${DIGEST}.spdx  \
-                ${{ env.ONLINE_REGISTER }}/${{ github.repository }}/${{ matrix.name }}${{ steps.ref.outputs.suffix }}@${DIGEST}
+                ${{ env.ONLINE_REGISTER }}/${{ github.repository }}-${{ matrix.name }}${{ steps.ref.outputs.suffix }}@${DIGEST}
 
       - name: Sign SBOM Images
         env:
@@ -137,9 +138,9 @@ jobs:
         run: |
           set -x
           DIGEST=${{ steps.docker_build_release.outputs.digest }}
-          image_name="${{ env.ONLINE_REGISTER }}/${{ github.repository }}/${{ matrix.name }}${{ steps.ref.outputs.suffix }}:${DIGEST/:/-}.sbom"
+          image_name="${{ env.ONLINE_REGISTER }}/${{ github.repository }}-${{ matrix.name }}${{ steps.ref.outputs.suffix }}:${DIGEST/:/-}.sbom"
           sbom_digest="sha256:$(docker buildx imagetools inspect --raw ${image_name} | sha256sum | head -c 64)"
-          cosign sign -y "${{ env.ONLINE_REGISTER }}/${{ github.repository }}/${{ matrix.name }}${{ steps.ref.outputs.suffix }}@${sbom_digest}"
+          cosign sign -y "${{ env.ONLINE_REGISTER }}/${{ github.repository }}-${{ matrix.name }}${{ steps.ref.outputs.suffix }}@${sbom_digest}"
 
       - name: Image Release Digest
         shell: bash
diff --git a/VERSION b/VERSION
index be5bf2ad1..01e994d3d 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-v0.4.1
\ No newline at end of file
+v0.4.0
\ No newline at end of file
diff --git a/charts/spiderpool/Chart.yaml b/charts/spiderpool/Chart.yaml
index 3fd6f9f65..c16f757c4 100644
--- a/charts/spiderpool/Chart.yaml
+++ b/charts/spiderpool/Chart.yaml
@@ -5,11 +5,11 @@ icon: https://raw.githubusercontent.com/spidernet-io/spiderpool/main/docs/images
 # application or library
 type: application
 # no need to modify this version , CI will auto update it with /VERSION
-version: 0.4.1
+version: 0.4.0
 # This field is informational, and has no impact on chart version calculations .
 # Leaving it unquoted can lead to parsing issues in some cases
 # no need to modify this version , CI will auto update it with /VERSION
-appVersion: "0.4.1"
+appVersion: "0.4.0"
 kubeVersion: ">= 1.16.0-0"
 description: ipam for kubernetes cni
 sources:
diff --git a/charts/spiderpool/crds/spiderpool.spidernet.io_spiderippools.yaml b/charts/spiderpool/crds/spiderpool.spidernet.io_spiderippools.yaml
index 83c6a0117..996b85b45 100644
--- a/charts/spiderpool/crds/spiderpool.spidernet.io_spiderippools.yaml
+++ b/charts/spiderpool/crds/spiderpool.spidernet.io_spiderippools.yaml
@@ -44,11 +44,6 @@ spec:
       jsonPath: .spec.disable
       name: DISABLE
       type: boolean
-    - description: AppNamespace
-      jsonPath: .spec.podAffinity.matchLabels['ipam\.spidernet\.io/app\-namespace']
-      name: APP-NAMESPACE
-      priority: 10
-      type: string
     name: v2beta1
     schema:
       openAPIV3Schema:
diff --git a/cmd/spiderpool/cmd/command_add.go b/cmd/spiderpool/cmd/command_add.go
index 119b3544d..1bd182f58 100644
--- a/cmd/spiderpool/cmd/command_add.go
+++ b/cmd/spiderpool/cmd/command_add.go
@@ -66,7 +66,7 @@ func CmdAdd(args *skel.CmdArgs) (err error) {
 
 	k8sArgs := K8sArgs{}
 	if err = types.LoadArgs(args.Args, &k8sArgs); nil != err {
-		err := fmt.Errorf("failed to load CNI ENV args: %w", err)
+		err := fmt.Errorf("failed to load CNI ENV args: %v", err)
 		logger.Error(err.Error())
 		return err
 	}
@@ -80,7 +80,7 @@ func CmdAdd(args *skel.CmdArgs) (err error) {
 
 	spiderpoolAgentAPI, err := cmd.NewAgentOpenAPIUnixClient(conf.IPAM.IPAMUnixSocketPath)
 	if nil != err {
-		err := fmt.Errorf("failed to create spiderpool-agent client: %w", err)
+		err := fmt.Errorf("failed to create spiderpool-agent client: %v", err)
 		logger.Error(err.Error())
 		return err
 	}
diff --git a/cmd/spiderpool/cmd/command_delete.go b/cmd/spiderpool/cmd/command_delete.go
index 298f72da2..ec3487da4 100644
--- a/cmd/spiderpool/cmd/command_delete.go
+++ b/cmd/spiderpool/cmd/command_delete.go
@@ -62,7 +62,7 @@ func CmdDel(args *skel.CmdArgs) (err error) {
 
 	k8sArgs := K8sArgs{}
 	if err = types.LoadArgs(args.Args, &k8sArgs); nil != err {
-		err := fmt.Errorf("failed to load CNI ENV args: %w", err)
+		err := fmt.Errorf("failed to load CNI ENV args: %v", err)
 		logger.Error(err.Error())
 		return err
 	}
@@ -76,7 +76,7 @@ func CmdDel(args *skel.CmdArgs) (err error) {
 
 	spiderpoolAgentAPI, err := cmd.NewAgentOpenAPIUnixClient(conf.IPAM.IPAMUnixSocketPath)
 	if nil != err {
-		err := fmt.Errorf("failed to create spiderpool-agent client: %w", err)
+		err := fmt.Errorf("failed to create spiderpool-agent client: %v", err)
 		logger.Error(err.Error())
 		return err
 	}
@@ -106,7 +106,7 @@ func CmdDel(args *skel.CmdArgs) (err error) {
 	logger.Debug("Send IPAM request")
 	_, err = spiderpoolAgentAPI.Daemonset.DeleteIpamIP(params)
 	if nil != err {
-		logger.Sugar().Errorf("%v: %v", ErrDeleteIPAM, err)
+		logger.Sugar().Errorf("%w: %v", ErrDeleteIPAM, err)
 		return nil
 	}
 
diff --git a/docs/concepts/annotation.md b/docs/concepts/annotation.md
index 9d64dce9b..8a5f7f0cb 100644
--- a/docs/concepts/annotation.md
+++ b/docs/concepts/annotation.md
@@ -1,51 +1,59 @@
 # Annotations
 
-Spiderpool provides annotations for configuring custom ippools and routes.
+Spiderpool provides annotations for configuring custom IPPools and routes.
 
-## Application annotations
-
-Once you set up SpiderSubnet feature, you can specify SpiderSubnet annotations for the application templates.
-For more details, please refer to [SpiderSubnet](../usage/spider-subnet.md)
+## Pod annotations
 
-Notice: The current version only supports to use one SpiderSubnet V4/V6 CR for one interface, you shouldn't specify 2 or more SpiderSubnet V4 CRs,
-and it will choose the first one to use.
+After enabling the feature [SpiderSubnet](../usage/spider-subnet.md) (default enabled after v0.4.0), the annotations related to Subnet will take effect. They always have higher priority than IPPool related annotations.
 
-### ipam.spidernet.io/subnets
+### ipam.spidernet.io/subnet
 
-Here's an example for multiple interfaces to use SpiderSubnet.
+Specify the Subnets used to generate IPPools and allocate IP addresses.
 
 ```yaml
-ipam.spidernet.io/subnets: |-
-  [{"interface": "eth0", "ipv4": ["subnet-demo-v4-1"], "ipv6": ["subnet-demo-v6-1"]},
-   {"interface": "net2", "ipv4": ["subnet-demo-v4-2"], "ipv6": ["subnet-demo-v6-2"]}]
+ipam.spidernet.io/subnet: |-
+  {
+    "ipv4": ["demo-v4-subnet1"],
+    "ipv6": ["demo-v6-subnet1"]
+  }
 ```
 
-### ipam.spidernet.io/subnet
+- `ipv4` (array, optional): Specify which Subnet is used to generate IPPool and allocate the IPv4 address. When `enableIPv4` in the ConfigMap `spiderpool-conf` is set to true, this field is required.
+- `ipv6` (array, optional): Specify which Subnet is used to generate IPPool and allocate the IPv6 address. When `enableIPv6` in the ConfigMap `spiderpool-conf` is set to true, this field is required.
 
-This is used for SpiderSubnet with single interface.
+### ipam.spidernet.io/subnets
 
 ```yaml
-ipam.spidernet.io/subnet: '{"ipv4": ["subnet-demo-v4"], "ipv6": ["subnet-demo-v6"]}'
+ipam.spidernet.io/subnets: |-
+  [{
+      "interface": "eth0",
+      "ipv4": ["demo-v4-subnet1"],
+      "ipv6": ["demo-v6-subnet1"]
+   },{
+      "interface": "net1",
+      "ipv4": ["demo-v4-subnet2"],
+      "ipv6": ["demo-v6-subnet2"]
+  }]
 ```
 
-## Pod annotations
-
-For a pod, you can specify Spiderpool annotations for a special request.
+- `interface` (string, required): Since the CNI request only carries the information of one interface, the field `interface` shall be specified to distinguish in the case of multiple interfaces.
+- `ipv4` (array, optional): Specify which Subnet is used to generate IPPool and allocate the IPv4 address. When `enableIPv4` in the ConfigMap `spiderpool-conf` is set to true, this field is required.
+- `ipv6` (array, optional): Specify which Subnet is used to generate IPPool and allocate the IPv6 address. When `enableIPv6` in the ConfigMap `spiderpool-conf` is set to true, this field is required.
 
 ### ipam.spidernet.io/ippool
 
-Specify the ippools used to allocate IP.
+Specify the IPPools used to allocate IP addresses.
 
 ```yaml
 ipam.spidernet.io/ippool: |-
   {
-    "ipv4": ["v4-ippool1"],
-    "ipv6": ["v6-ippool1", "v6-ippool2"]
+    "ipv4": ["demo-v4-ippool1"],
+    "ipv6": ["demo-v6-ippool1", "demo-v6-ippool2"]
   }
 ```
 
-- `ipv4` (array, optional): Specify which ippool is used to allocate the IPv4 address. When `enableIPv4` in the `spiderpool-conf` ConfigMap is set to true, this field is required.
-- `ipv6` (array, optional): Specify which ippool is used to allocate the IPv6 address. When `enableIPv6` in the `spiderpool-conf` ConfigMap is set to true, this field is required.
+- `ipv4` (array, optional): Specify which IPPool is used to allocate the IPv4 address. When `enableIPv4` in the ConfigMap `spiderpool-conf` is set to true, this field is required.
+- `ipv6` (array, optional): Specify which IPPool is used to allocate the IPv6 address. When `enableIPv6` in the ConfigMap `spiderpool-conf` is set to true, this field is required.
 
 ### ipam.spidernet.io/ippools
 
@@ -55,23 +63,21 @@ It is similar to `ipam.spidernet.io/ippool` but could be used in the case with m
 ipam.spidernet.io/ippools: |-
   [{
       "interface": "eth0",
-      "ipv4": ["v4-ippool1"],
-      "ipv6": ["v6-ippool1"],
+      "ipv4": ["demo-v4-ippool1"],
+      "ipv6": ["demo-v6-ippool1"],
       "cleangateway": true
    },{
-      "interface": "eth1",
-      "ipv4": ["v4-ippool2"],
-      "ipv6": ["v6-ippool2"],
+      "interface": "net1",
+      "ipv4": ["demo-v4-ippool2"],
+      "ipv6": ["demo-v6-ippool2"],
       "cleangateway": false
   }]
 ```
 
-- `interface` (string, required): Since the CNI request only carries the information of one interface, the `interface` field shall be specified to distinguish in the case of multiple interfaces.
-- `ipv4` (array, optional): Specify which ippool is used to allocate the IPv4 address. When `enableIPv4` in the `spiderpool-conf` ConfigMap is set to true, this field is required.
-- `ipv6` (array, optional): Specify which ippool is used to allocate the IPv6 address. When `enableIPv6` in the `spiderpool-conf` ConfigMap is set to true, this field is required.
-- `cleangateway` (bool, optional): If set to true, the IPAM plugin will not return the default gateway route recorded in the ippool. default to false
-
-For different interfaces, it is not recommended to use ippools of the same subnet.
+- `interface` (string, required): Since the CNI request only carries the information of one interface, the field `interface` shall be specified to distinguish in the case of multiple interfaces.
+- `ipv4` (array, optional): Specify which IPPool is used to allocate the IPv4 address. When `enableIPv4` in the ConfigMap `spiderpool-conf` is set to true, this field is required.
+- `ipv6` (array, optional): Specify which IPPool is used to allocate the IPv6 address. When `enableIPv6` in the ConfigMap `spiderpool-conf` is set to true, this field is required.
+- `cleangateway` (bool, optional): If set to true, the IPAM plugin will not return the default route (generated by `spec.gateway`) recorded in the IPPool.
 
 ### ipam.spidernet.io/routes
 
@@ -82,31 +88,18 @@ ipam.spidernet.io/routes: |-
   [{
       "dst": "10.0.0.0/16",
       "gw": "192.168.1.1"
+  },{
+      "dst": "172.10.40.0/24",
+      "gw": "172.18.40.1"
   }]
 ```
 
 - `dst` (string, required): Network destination of the route.
 - `gw` (string, required): The forwarding or next hop IP address.
 
-### ipam.spidernet.io/assigned-{INTERFACE}
-
-It is the IP allocation result of the interface. It is only used by Spiderpool, not reserved for users.
-
-```yaml
-ipam.spidernet.io/assigned-eth0: |-
-  {
-    "interface": "eth0",
-    "ipv4pool": "v4-ippool1",
-    "ipv6pool": "v6-ippool1",
-    "ipv4": "172.16.0.100/16",
-    "ipv6": "fd00::100/64",
-    "vlan": 100
-  }
-```
-
 ## Namespace annotations
 
-Namespace could set following annotations to specify default ippools. They are valid for all Pods under the Namespace.
+A Namespace can set the following annotations to specify default IPPools which are effective for all Pods under the Namespace.
 
 ### ipam.spidernet.io/default-ipv4-ippool
 
@@ -114,12 +107,8 @@ Namespace could set following annotations to specify default ippools. They are v
 ipam.spidernet.io/default-ipv4-ippool: '["ns-v4-ippool1","ns-v4-ippool2"]'
 ```
 
-If multiple ippools are listed, it will try to allocate IP from the later ippool when the former one is not allocatable.
-
 ### ipam.spidernet.io/default-ipv6-ippool
 
 ```yaml
 ipam.spidernet.io/default-ipv6-ippool: '["ns-v6-ippool1","ns-v6-ippool2"]'
 ```
-
-For other procedure, similar to [Pod Annotations](#pod-annotations) described above.
diff --git a/docs/develop/release.md b/docs/develop/release.md
index 0346667af..a7057e945 100644
--- a/docs/develop/release.md
+++ b/docs/develop/release.md
@@ -3,15 +3,24 @@
 ## pre-steps
 
 * update 'version' and 'appVersion' filed in 'charts/*/Chart.yaml'
+
 * update version in '/VERSION'
+
 * a version tag should be set on right branch. The version should go with 
-  * v0.1.0-rc0
-  * v0.1.0-rc1
-  * v0.1.0
-  * v0.1.1
-  * v0.1.2
-  * v0.2.0-rc0
-  * v0.2.0
+
+    * v0.1.0-rc0
+
+    * v0.1.0-rc1
+
+    * v0.1.0
+  
+    * v0.1.1
+  
+    * v0.1.2
+  
+    * v0.2.0-rc0
+  
+    * v0.2.0
 
 ## push a version tag
 
diff --git a/docs/usage/performance-zh_CH.md b/docs/usage/performance-zh_CH.md
index 63d34648b..d1b28cd21 100644
--- a/docs/usage/performance-zh_CH.md
+++ b/docs/usage/performance-zh_CH.md
@@ -36,7 +36,7 @@
 
 | Main CNI            | Main CNI 版本 | IPAM CNI                  | IPAM CNI 版本 | 特点                                                         |
 | ------------------- | ------------- | ------------------------- | ------------- | ------------------------------------------------------------ |
-| macvlan             | `v1.1.1`      | Spiderpool                | `v0.4.1`      | 集群中存在多个 IP 池，每个池中的 IP 地址都可以被集群中的任意一个节点上的 Pod 所使用，当集群中的多个 Pod 并发的从同一个池中分配 IP 地址时，存在竞争。支持托管 IP 池的全生命流程，使其同步的与工作负载创建、扩缩容、删除，弱化了过大的共享池所带来的并发或存储问题。 |
+| macvlan             | `v1.1.1`      | Spiderpool                | `v0.4.0`      | 集群中存在多个 IP 池，每个池中的 IP 地址都可以被集群中的任意一个节点上的 Pod 所使用，当集群中的多个 Pod 并发的从同一个池中分配 IP 地址时，存在竞争。支持托管 IP 池的全生命流程，使其同步的与工作负载创建、扩缩容、删除，弱化了过大的共享池所带来的并发或存储问题。 |
 | macvlan             | `v1.1.1`      | Whereabouts (CRD backend) | `v0.6.1`      | 各节点可以定义各自可用的 IP 池范围，若节点间存在重复定义的 IP 地址，那么这些 IP 地址上升为一种共享资源。 |
 | Kube-OVN (underlay) | `v1.11.3`     | Kube-OVN                  | `v1.11.3`     | 以子网来组织 IP 地址，每个 Namespace 可以归属于特定的子网， Namespace 下的 Pod 会自动从所属的子网中获取 IP 地址。子网也是一种集群资源，同一个子网的 IP 地址可以分布在任意一个节点上。 |
 | Calico              | `v3.23.3`     | calico-ipam (CRD backend) | `v3.23.3`     | 每个节点独享一个或多个 IP block，各节点上的 Pod 仅使用本地 IP block 中的 IP 地址，节点间无任何竞争与冲突，分配的效率非常高。 |
@@ -53,14 +53,12 @@
 - 仅创建一个 Deployment，其副本数为 1000。
 - 创建 100 个 Deployment，每个 Deployment 的副本数为 10。
 
-然后，我们会使用如下命令一次性的删除这 1000 个 Pod，记录被重建的 1000 个 Pod 全部 `Running` 的耗时：
+接下来，我们会使用如下命令一次性的删除这 1000 个 Pod，记录被重建的 1000 个 Pod 再次全部达到 `Running` 的耗时：
 
 ```bash
 kubectl get pod | grep "prefix" | awk '{print $1}' | xargs kubectl delete pod
 ```
 
-接下来，将所有节点下电后再上电，模拟故障恢复，记录 1000 个 Pod 再次达到 `Running` 的耗时。
-
 最后，我们删除所有的 Deployment，记录所有 Pod 完全消失的耗时。
 
 ## 结果
@@ -69,44 +67,44 @@ kubectl get pod | grep "prefix" | awk '{print $1}' | xargs kubectl delete pod
 
 - 单个 1000 副本的 Deployment：
 
-  | CNI                   | 创建   | 重建  | 故障恢复 | 删除  |
-  | --------------------- | ------ | ----- | -------- | ----- |
-  | macvlan + Spiderpool  | 2m35s  | 9m50s | 3m4s     | 1m50s |
-  | macvlan + Whereabouts | 25m18s | 失败  | 失败     | 3m5s  |
-  | Kube-OVN              | 3m55s  | 7m20s | 11m6s    | 2m13s |
-  | Calico + calico-ipam  | 1m56s  | 4m6s  | 3m42s    | 1m36s |
+  | CNI                   | 创建   | 重建  | 删除  |
+  | --------------------- | ------ | ----- | ----- |
+  | macvlan + Spiderpool  | 2m35s  | 9m50s | 1m50s |
+  | macvlan + Whereabouts | 25m18s | 失败  | 3m5s  |
+  | Kube-OVN              | 3m55s  | 7m20s | 2m13s |
+  | Calico + calico-ipam  | 1m56s  | 4m6s  | 1m36s |
 
   > 在测试 macvlan + Whereabouts 这个组合期间，创建的场景中 922 个 Pod 在 14m25s 内以较为均匀的速率达到 `Running` 状态，自此之后的 Pod 增长速率大幅降低，最终 1000 个 Pod 花了 25m18s 达到 `Running` 状态。至于重建的场景，在 55 个 Pod 达到 `Running` 状态后，Whereabouts 就基本不工作了，耗时类比于正无穷。
 
 - 100 个 10 副本的 Deployment：
 
-  | CNI                   | 创建   | 重建  | 故障恢复 | 删除  |
-  | --------------------- | ------ | ----- | -------- | ----- |
-  | macvlan + Spiderpool  | 1m37s  | 3m27s | 3m3s     | 1m22s |
-  | macvlan + Whereabouts | 21m49s | 失败  | 失败     | 2m9s  |
-  | Kube-OVN              | 4m6s   | 7m46s | 10m22s   | 2m8s  |
-  | Calico + calico-ipam  | 1m57s  | 3m58s | 4m16s    | 1m35s |
+  | CNI                   | 创建   | 重建  | 删除  |
+  | --------------------- | ------ | ----- | ----- |
+  | macvlan + Spiderpool  | 1m37s  | 3m27s | 1m22s |
+  | macvlan + Whereabouts | 21m49s | 失败  | 2m9s  |
+  | Kube-OVN              | 4m6s   | 7m46s | 2m8s  |
+  | Calico + calico-ipam  | 1m57s  | 3m58s | 1m35s |
 
 ### IPv4 单栈
 
 - 单个 1000 副本的 Deployment：
 
-  | CNI                   | 创建  | 重建  | 故障恢复 | 删除  |
-  | --------------------- | ----- | ----- | -------- | ----- |
-  | macvlan + Spiderpool  | 2m18s | 6m41s | 3m1s     | 1m37s |
-  | macvlan + Whereabouts | 8m16s | 失败  | 失败     | 2m7s  |
-  | Kube-OVN              | 3m32s | 7m7s  | 9m41s    | 1m47s |
-  | Calico + calico-ipam  | 1m41s | 3m33s | 3m42s    | 1m27s |
+  | CNI                   | 创建  | 重建  | 删除  |
+  | --------------------- | ----- | ----- | ----- |
+  | macvlan + Spiderpool  | 2m18s | 6m41s | 1m37s |
+  | macvlan + Whereabouts | 8m16s | 失败  | 2m7s  |
+  | Kube-OVN              | 3m32s | 7m7s  | 1m47s |
+  | Calico + calico-ipam  | 1m41s | 3m33s | 1m27s |
 
 
 - 100 个 10 副本的 Deployment：
 
-  | CNI                   | 创建  | 重建  | 故障恢复 | 删除  |
-  | --------------------- | ----- | ----- | -------- | ----- |
-  | macvlan + Spiderpool  | 1m4s  | 3m23s | 3m3s     | 1m23s |
-  | macvlan + Whereabouts | 8m13s | 失败  | 失败     | 2m7s  |
-  | Kube-OVN              | 3m36s | 7m14s | 8m52s    | 1m41s |
-  | Calico + calico-ipam  | 1m39s | 3m25s | 4m24s    | 1m27s |
+  | CNI                   | 创建  | 重建  | 删除  |
+  | --------------------- | ----- | ----- | ----- |
+  | macvlan + Spiderpool  | 1m4s  | 3m23s | 1m23s |
+  | macvlan + Whereabouts | 8m13s | 失败  | 2m7s  |
+  | Kube-OVN              | 3m36s | 7m14s | 1m41s |
+  | Calico + calico-ipam  | 1m39s | 3m25s | 1m27s |
 
 ## 小结
 
diff --git a/docs/usage/performance.md b/docs/usage/performance.md
index 3663f3004..d1d004da1 100644
--- a/docs/usage/performance.md
+++ b/docs/usage/performance.md
@@ -36,7 +36,7 @@ This test is based on the [CNI Specification](https://www.cni.dev/docs/spec/) `0
 
 | Main CNI            | Main CNI Version | IPAM CNI                  | IPAM CNI Version | Features                                                     |
 | ------------------- | ---------------- | ------------------------- | ---------------- | ------------------------------------------------------------ |
-| macvlan             | `v1.1.1`         | Spiderpool                | `v0.4.1`         | There are multiple IP pools in a cluster, and the IP addresses in each pool can be used by Pods on any Node in the cluster. Competition occurs when multiple Pods in a cluster allocate IP addresses from the same pool. Support hosting the entire lifecycle of an IP pool, synchronizing it with workload creation, scaling, deletion, and reducing concurrency or storage issues caused by overly large shared pools. |
+| macvlan             | `v1.1.1`         | Spiderpool                | `v0.4.0`         | There are multiple IP pools in a cluster, and the IP addresses in each pool can be used by Pods on any Node in the cluster. Competition occurs when multiple Pods in a cluster allocate IP addresses from the same pool. Support hosting the entire lifecycle of an IP pool, synchronizing it with workload creation, scaling, deletion, and reducing concurrency or storage issues caused by overly large shared pools. |
 | macvlan             | `v1.1.1`         | Whereabouts (CRD backend) | `v0.6.1`         | Each Node can define its own available IP pool ranges. If there are duplicate IP addresses defined between Nodes, these IP addresses are promoted as a shared resource. |
 | Kube-OVN (underlay) | `v1.11.3`        | Kube-OVN                  | `v1.11.3`        | IP addresses are organized by Subnet. Each Namespace can belong to a specific Subnet. Pods under the Namespace will automatically obtain IP addresses from the Subnet they belong to. Subnets are also a cluster resource, and the IP addresses of the same Subnet can be distributed on any Node. |
 | Calico              | `v3.23.3`        | calico-ipam (CRD backend) | `v3.23.3`        | Each Node has one or more IP blocks exclusively, and the Pods on each Node only use the IP addresses in the local IP block. There is no competition or conflict between Nodes, and the allocation efficiency is very high. |
@@ -53,14 +53,12 @@ Specifically, we will attempt to create a total of 1000 Pods on the Kubernetes c
 - Create only one Deployment with 1000 replicas.
 - Create 100 Deployments with 10 replicas per Deployment.
 
-Then, we will use the following command to delete these 1000 Pods at once, and record the time it took for all Pods to become `Running`:
+Next, we will use the following command to delete these 1000 Pods at once, and record the time it took for all Pods to become `Running` again:
 
 ```bash
 kubectl get pod | grep "prefix" | awk '{print $1}' | xargs kubectl delete pod
 ```
 
-Next, power down all nodes and then power up, simulate fault recovery, and record the time it took for all Pods to become `Running` again.
-
 Finally, we delete all Deployments and record the time taken for all Pods to completely disappear.
 
 ## Result
@@ -69,44 +67,44 @@ Finally, we delete all Deployments and record the time taken for all Pods to com
 
 - 1 Deployment with 1000 replicas: 
 
-  | CNI                   | Creation | Re-creation | Recovery | Deletion |
-  | --------------------- | -------- | ----------- | -------- | -------- |
-  | macvlan + Spiderpool  | 2m35s    | 9m50s       | 3m4s     | 1m50s    |
-  | macvlan + Whereabouts | 25m18s   | failure     | failure  | 3m5s     |
-  | Kube-OVN              | 3m55s    | 7m20s       | 11m6s    | 2m13s    |
-  | Calico + calico-ipam  | 1m56s    | 4m6s        | 3m42s    | 1m36s    |
+  | CNI                   | Creation | Re-creation | Deletion |
+  | --------------------- | -------- | ----------- | -------- |
+  | macvlan + Spiderpool  | 2m35s    | 9m50s       | 1m50s    |
+  | macvlan + Whereabouts | 25m18s   | failure     | 3m5s     |
+  | Kube-OVN              | 3m55s    | 7m20s       | 2m13s    |
+  | Calico + calico-ipam  | 1m56s    | 4m6s        | 1m36s    |
 
   > During the testing of macvlan + Whereabouts, in the creation scenario, 922 Pods became `Running` at a relatively uniform rate within 14m25s. After that, the growth rate of Pods significantly decreased, and ultimately it took 25m18s for 1000 Pods to become `Running`. As for the re-creation scenario, after 55 Pods became `Running`, Whereabouts basically stopped working, and the time consumption was close to infinity.
 
 - 100 Deployments with 10 replicas: 
 
-  | CNI                   | Creation | Re-creation | Recovery | Deletion |
-  | --------------------- | -------- | ----------- | -------- | -------- |
-  | macvlan + Spiderpool  | 1m37s    | 3m27s       | 3m3s     | 1m22s    |
-  | macvlan + Whereabouts | 21m49s   | failure     | failure  | 2m9s     |
-  | Kube-OVN              | 4m6s     | 7m46s       | 10m22s   | 2m8s     |
-  | Calico + calico-ipam  | 1m57s    | 3m58s       | 4m16s    | 1m35s    |
+  | CNI                   | Creation | Re-creation | Deletion |
+  | --------------------- | -------- | ----------- | -------- |
+  | macvlan + Spiderpool  | 1m37s    | 3m27s       | 1m22s    |
+  | macvlan + Whereabouts | 21m49s   | failure     | 2m9s     |
+  | Kube-OVN              | 4m6s     | 7m46s       | 2m8s     |
+  | Calico + calico-ipam  | 1m57s    | 3m58s       | 1m35s    |
 
 ### IPv4 stack
 
-- 1 Deployment with 1000 replicas: 
+- 1 Deployment with 1000 replicas : 
 
-  | CNI                   | Creation | Re-creation | Recovery | Deletion |
-  | --------------------- | -------- | ----------- | -------- | -------- |
-  | macvlan + Spiderpool  | 2m18s    | 6m41s       | 3m1s     | 1m37s    |
-  | macvlan + Whereabouts | 8m16s    | failure     | failure  | 2m7s     |
-  | Kube-OVN              | 3m32s    | 7m7s        | 9m41s    | 1m47s    |
-  | Calico + calico-ipam  | 1m41s    | 3m33s       | 3m42s    | 1m27s    |
+  | CNI                   | Creation | Re-creation | Deletion |
+  | --------------------- | -------- | ----------- | -------- |
+  | macvlan + Spiderpool  | 2m18s    | 6m41s       | 1m37s    |
+  | macvlan + Whereabouts | 8m16s    | failure     | 2m7s     |
+  | Kube-OVN              | 3m32s    | 7m7s        | 1m47s    |
+  | Calico + calico-ipam  | 1m41s    | 3m33s       | 1m27s    |
 
 
-- 100 Deployments with 10 replicas per Deployment: 
+- 100 Deployments and 10 replicas for each deployment : 
 
-  | CNI                   | Creation | Re-creation | Recovery | Deletion |
-  | --------------------- | -------- | ----------- | -------- | -------- |
-  | macvlan + Spiderpool  | 1m4s     | 3m23s       | 3m3s     | 1m23s    |
-  | macvlan + Whereabouts | 8m13s    | failure     | failure  | 2m7s     |
-  | Kube-OVN              | 3m36s    | 7m14s       | 8m52s    | 1m41s    |
-  | Calico + calico-ipam  | 1m39s    | 3m25s       | 4m24s    | 1m27s    |
+  | CNI                   | Creation | Re-creation | Deletion |
+  | --------------------- | -------- | ----------- | -------- |
+  | macvlan + Spiderpool  | 1m4s     | 3m23s       | 1m23s    |
+  | macvlan + Whereabouts | 8m13s    | failure     | 2m7s     |
+  | Kube-OVN              | 3m36s    | 7m14s       | 1m41s    |
+  | Calico + calico-ipam  | 1m39s    | 3m25s       | 1m27s    |
 
 ## Summary
 
diff --git a/go.mod b/go.mod
index 5910c24cf..a7006abbd 100644
--- a/go.mod
+++ b/go.mod
@@ -46,7 +46,6 @@ require (
 
 require (
 	github.com/agiledragon/gomonkey/v2 v2.9.0
-	github.com/containernetworking/plugins v1.2.0
 	github.com/golang/mock v1.6.0
 	github.com/openkruise/kruise-api v1.3.0
 	github.com/prometheus/client_golang v1.14.0
diff --git a/go.sum b/go.sum
index a1b5874dc..a7b693dd0 100644
--- a/go.sum
+++ b/go.sum
@@ -74,8 +74,6 @@ github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnht
 github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 github.com/containernetworking/cni v1.1.2 h1:wtRGZVv7olUHMOqouPpn3cXJWpJgM6+EUl31EQbXALQ=
 github.com/containernetworking/cni v1.1.2/go.mod h1:sDpYKmGVENF3s6uvMvGgldDWeG8dMxakj/u+i9ht9vw=
-github.com/containernetworking/plugins v1.2.0 h1:SWgg3dQG1yzUo4d9iD8cwSVh1VqI+bP7mkPDoSfP9VU=
-github.com/containernetworking/plugins v1.2.0/go.mod h1:/VjX4uHecW5vVimFa1wkG4s+r/s9qIfPdqlLF4TW8c4=
 github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
 github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
diff --git a/pkg/applicationcontroller/app_controller.go b/pkg/applicationcontroller/app_controller.go
index 1b446fdc2..5bb7e99d9 100644
--- a/pkg/applicationcontroller/app_controller.go
+++ b/pkg/applicationcontroller/app_controller.go
@@ -609,6 +609,7 @@ func (sac *SubnetAppController) syncHandler(appKey appWorkQueueKey, log *zap.Log
 	var app metav1.Object
 	var subnetConfig *types.PodSubnetAnnoConfig
 	var podAnno map[string]string
+	var podSelector *metav1.LabelSelector
 	var appReplicas int
 	var apiVersion string
 
@@ -624,6 +625,7 @@ func (sac *SubnetAppController) syncHandler(appKey appWorkQueueKey, log *zap.Log
 		}
 
 		podAnno = deployment.Spec.Template.Annotations
+		podSelector = deployment.Spec.Selector
 		appReplicas = applicationinformers.GetAppReplicas(deployment.Spec.Replicas)
 		app = deployment.DeepCopy()
 		// deployment.APIVersion is empty string
@@ -640,6 +642,7 @@ func (sac *SubnetAppController) syncHandler(appKey appWorkQueueKey, log *zap.Log
 		}
 
 		podAnno = replicaSet.Spec.Template.Annotations
+		podSelector = replicaSet.Spec.Selector
 		appReplicas = applicationinformers.GetAppReplicas(replicaSet.Spec.Replicas)
 		app = replicaSet.DeepCopy()
 		// replicaSet.APIVersion is empty string
@@ -656,6 +659,7 @@ func (sac *SubnetAppController) syncHandler(appKey appWorkQueueKey, log *zap.Log
 		}
 
 		podAnno = daemonSet.Spec.Template.Annotations
+		podSelector = daemonSet.Spec.Selector
 		appReplicas = int(daemonSet.Status.DesiredNumberScheduled)
 		app = daemonSet.DeepCopy()
 		// daemonSet.APIVersion is empty string
@@ -672,6 +676,7 @@ func (sac *SubnetAppController) syncHandler(appKey appWorkQueueKey, log *zap.Log
 		}
 
 		podAnno = statefulSet.Spec.Template.Annotations
+		podSelector = statefulSet.Spec.Selector
 		appReplicas = applicationinformers.GetAppReplicas(statefulSet.Spec.Replicas)
 		app = statefulSet.DeepCopy()
 		// statefulSet.APIVersion is empty string
@@ -688,6 +693,7 @@ func (sac *SubnetAppController) syncHandler(appKey appWorkQueueKey, log *zap.Log
 		}
 
 		podAnno = job.Spec.Template.Annotations
+		podSelector = job.Spec.Selector
 		appReplicas = applicationinformers.CalculateJobPodNum(job.Spec.Parallelism, job.Spec.Completions)
 		app = job.DeepCopy()
 		// job.APIVersion is empty string
@@ -704,6 +710,7 @@ func (sac *SubnetAppController) syncHandler(appKey appWorkQueueKey, log *zap.Log
 		}
 
 		podAnno = cronJob.Spec.JobTemplate.Spec.Template.Annotations
+		podSelector = cronJob.Spec.JobTemplate.Spec.Selector
 		appReplicas = applicationinformers.CalculateJobPodNum(cronJob.Spec.JobTemplate.Spec.Parallelism, cronJob.Spec.JobTemplate.Spec.Completions)
 		app = cronJob.DeepCopy()
 		// cronJob.APIVersion is empty string
@@ -731,6 +738,7 @@ func (sac *SubnetAppController) syncHandler(appKey appWorkQueueKey, log *zap.Log
 			UID: app.GetUID(),
 			APP: app,
 		},
+		podSelector,
 		appReplicas)
 	if nil != err {
 		return fmt.Errorf("failed to create or scale IPPool: %w", err)
@@ -741,49 +749,18 @@ func (sac *SubnetAppController) syncHandler(appKey appWorkQueueKey, log *zap.Log
 
 // applyAutoIPPool try to create an IPPool or mark IPPool desired IP number with the give SpiderSubnet configuration
 func (sac *SubnetAppController) applyAutoIPPool(ctx context.Context, podSubnetConfig types.PodSubnetAnnoConfig,
-	podController types.PodTopController, appReplicas int) error {
+	podController types.PodTopController, podSelector *metav1.LabelSelector, appReplicas int) error {
 	log := logutils.FromContext(ctx)
 
 	// retrieve application pools
-	fn := func(subnetName string, ipVersion types.IPVersion, ifName string) error {
-		var ipVersionStr string
-		if ipVersion == constant.IPv4 {
-			ipVersionStr = constant.LabelValueIPVersionV4
-		} else {
-			ipVersionStr = constant.LabelValueIPVersionV6
-		}
-
-		var tmpPool *spiderpoolv2beta1.SpiderIPPool
-		tmpPoolList := &spiderpoolv2beta1.SpiderIPPoolList{}
-		matchLabels := client.MatchingLabels{
-			constant.LabelIPPoolOwnerSpiderSubnet: subnetName,
-			constant.LabelIPPoolOwnerApplication:  applicationinformers.ApplicationNamespacedName(podController.AppNamespacedName),
-			constant.LabelIPPoolIPVersion:         ipVersionStr,
-			constant.LabelIPPoolInterface:         ifName,
-		}
-
-		err := sac.apiReader.List(ctx, tmpPoolList, matchLabels)
+	fn := func(poolName string, subnetName string, ipVersion types.IPVersion, ifName string) (err error) {
+		tmpPool := &spiderpoolv2beta1.SpiderIPPool{}
+		err = sac.apiReader.Get(ctx, k8types.NamespacedName{Name: poolName}, tmpPool)
 		if nil != err {
-			return fmt.Errorf("failed to get auto-created IPPoolList with matchLabels %v, error: %w", matchLabels, err)
-		}
-
-		if len(tmpPoolList.Items) == 0 {
-			tmpPool = nil
-		} else {
-			for i := range tmpPoolList.Items {
-				// We need to ignore the previous same NamespacedName application corresponding auto-created IPPool.
-				// Because this auto-created IPPool will be deleted by the system with 'ippool-reclaim'
-				labels := tmpPoolList.Items[i].GetLabels()
-				if labels[constant.LabelIPPoolReclaimIPPool] == constant.True && labels[constant.LabelIPPoolOwnerApplicationUID] != string(podController.UID) {
-					log.Sugar().Debugf("found the previous same app auto-created IPPool %s", tmpPoolList.Items[i].Name)
-					continue
-				}
-
-				// If 'ippool-reclaim' is true, we'll go to scale the auto-created IPPool because of the same application UID.
-				// If 'ippool-reclaim' is false, we'll reuse this auto-crated IPPool and refresh its application UID label.
-				tmpPool = tmpPoolList.Items[i].DeepCopy()
-				log.Sugar().Debugf("found reuse app auto-created IPPool %s", tmpPool.Name)
-				break
+			if apierrors.IsNotFound(err) {
+				tmpPool = nil
+			} else {
+				return fmt.Errorf("failed to get auto-created IPPool '%s': %w", poolName, err)
 			}
 		}
 
@@ -794,19 +771,17 @@ func (sac *SubnetAppController) applyAutoIPPool(ctx context.Context, podSubnetCo
 			desiredIPNumber = podSubnetConfig.AssignIPNum
 		}
 
-		log.Sugar().Infof("try to reconcile auto-created IPv%d IPPool for Interface %s by SpiderSubnet %s with application controller %v",
-			ipVersion, ifName, subnetName, podController.AppNamespacedName)
-		_, err = sac.subnetMgr.ReconcileAutoIPPool(ctx, tmpPool, subnetName, podController, types.AutoPoolProperty{
+		log.Sugar().Infof("try to reconcile auto-created IPPool %s", poolName)
+
+		_, err = sac.subnetMgr.ReconcileAutoIPPool(ctx, tmpPool.DeepCopy(), subnetName, podController, types.AutoPoolProperty{
 			DesiredIPNumber: desiredIPNumber,
 			IPVersion:       ipVersion,
 			IsReclaimIPPool: podSubnetConfig.ReclaimIPPool,
 			IfName:          ifName,
+			PodSelector:     podSelector,
 		})
-		if nil != err {
-			return err
-		}
 
-		return nil
+		return
 	}
 
 	processNext := func(item types.AnnoSubnetItem) error {
@@ -823,14 +798,19 @@ func (sac *SubnetAppController) applyAutoIPPool(ctx context.Context, podSubnetCo
 			wg.Add(1)
 			go func() {
 				defer wg.Done()
-				errV4 = fn(item.IPv4[0], constant.IPv4, item.Interface)
+
+				v4PoolName := applicationinformers.SubnetPoolName(podController.Name, constant.IPv4, item.Interface, podController.UID)
+				errV4 = fn(v4PoolName, item.IPv4[0], constant.IPv4, item.Interface)
 			}()
 		}
+
 		if sac.EnableIPv6 && len(item.IPv6) != 0 {
 			wg.Add(1)
 			go func() {
 				defer wg.Done()
-				errV6 = fn(item.IPv6[0], constant.IPv6, item.Interface)
+
+				v6PoolName := applicationinformers.SubnetPoolName(podController.Name, constant.IPv6, item.Interface, podController.UID)
+				errV6 = fn(v6PoolName, item.IPv6[0], constant.IPv6, item.Interface)
 			}()
 		}
 
diff --git a/pkg/applicationcontroller/applicationinformers/utils.go b/pkg/applicationcontroller/applicationinformers/utils.go
index 8deac045a..0e901c1c9 100644
--- a/pkg/applicationcontroller/applicationinformers/utils.go
+++ b/pkg/applicationcontroller/applicationinformers/utils.go
@@ -37,67 +37,31 @@ var errInvalidInput = func(str string) error {
 	return fmt.Errorf("invalid input '%s'", str)
 }
 
-const (
-	maxNameLength = 63
-	randomLength  = 5
-)
-
-func AutoPoolName(controllerName string, ipVersion types.IPVersion, ifName string, appUID apitypes.UID) string {
+func SubnetPoolName(controllerName string, ipVersion types.IPVersion, ifName string, controllerUID apitypes.UID) string {
 	// the format of uuid is "xxxx-xxxx-xxxx-xxxxxxxxxxxx"
 	// ref: https://github.com/google/uuid/blob/44b5fee7c49cf3bcdf723f106b36d56ef13ccc88/uuid.go#L185
-	splits := strings.Split(string(appUID), "-")
+	splits := strings.Split(string(controllerUID), "-")
 	lastOne := splits[len(splits)-1]
-	// we constrain the max random length to 5
-	if len(lastOne) > randomLength {
-		lastOne = lastOne[:randomLength]
-	}
-
-	// 7 means "auto${IPVersion}" prefix and 2 bound symbol "-" length
-	maxCustomNameLength := maxNameLength - len(lastOne) - 7
-	if len(controllerName+ifName) > maxCustomNameLength {
-		controllerName = controllerName[:maxCustomNameLength-len(ifName)]
-	}
 
-	return fmt.Sprintf("auto%d-%s-%s-%s", ipVersion, strings.ToLower(controllerName), strings.ToLower(ifName), strings.ToLower(lastOne))
+	return fmt.Sprintf("auto-%s-v%d-%s-%s", strings.ToLower(controllerName), ipVersion, ifName, strings.ToLower(lastOne))
 }
 
 // ApplicationNamespacedName will joint the application apiVersion, application type, namespace and name as a string, then we need unpack it for tracing
 // [ns and object name constraint Ref]: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/
 // We set format is "{apiVersion}:{appKind}:{appNS}:{appName}"
 func ApplicationNamespacedName(appNamespacedName types.AppNamespacedName) string {
-	// Kubernetes API Group might be empty, ref: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#api-versions
-	first, second, hasGroup := strings.Cut(appNamespacedName.APIVersion, "/")
-	if hasGroup {
-		return fmt.Sprintf("%s_%s_%s_%s_%s", first, second, appNamespacedName.Kind, appNamespacedName.Namespace, appNamespacedName.Name)
-	}
-
-	return fmt.Sprintf("%s_%s_%s_%s", first, appNamespacedName.Kind, appNamespacedName.Namespace, appNamespacedName.Name)
+	return fmt.Sprintf("%s:%s:%s:%s", appNamespacedName.APIVersion, appNamespacedName.Kind, appNamespacedName.Namespace, appNamespacedName.Name)
 }
 
 // ParseApplicationNamespacedName will unpack the appNamespacedNameKey, its corresponding function is ApplicationNamespacedName
 func ParseApplicationNamespacedName(appNamespacedNameKey string) (appNamespacedName types.AppNamespacedName, isMatch bool) {
-	split := strings.Split(appNamespacedNameKey, "_")
-
-	// no API Group
+	split := strings.Split(appNamespacedNameKey, ":")
 	if len(split) == 4 {
 		return types.AppNamespacedName{
-			APIVersion: schema.GroupVersion{
-				Group:   "",
-				Version: split[0],
-			}.String(),
-			Kind:      split[1],
-			Namespace: split[2],
-			Name:      split[3],
-		}, true
-	} else if len(split) == 5 {
-		return types.AppNamespacedName{
-			APIVersion: schema.GroupVersion{
-				Group:   split[0],
-				Version: split[1],
-			}.String(),
-			Kind:      split[2],
-			Namespace: split[3],
-			Name:      split[4],
+			APIVersion: split[0],
+			Kind:       split[1],
+			Namespace:  split[2],
+			Name:       split[3],
 		}, true
 	}
 
diff --git a/pkg/constant/k8s.go b/pkg/constant/k8s.go
index da9b9ae9a..5ad36435c 100644
--- a/pkg/constant/k8s.go
+++ b/pkg/constant/k8s.go
@@ -61,26 +61,14 @@ const (
 	AnnoSpiderSubnets             = AnnotationPre + "/subnets"
 	AnnoSpiderSubnetPoolIPNumber  = AnnotationPre + "/ippool-ip-number"
 	AnnoSpiderSubnetReclaimIPPool = AnnotationPre + "/ippool-reclaim"
+	AnnoSpiderSubnetPoolApp       = AnnotationPre + "/application"
 
-	LabelIPPoolReclaimIPPool       = AnnoSpiderSubnetReclaimIPPool
 	LabelIPPoolOwnerSpiderSubnet   = AnnotationPre + "/owner-spider-subnet"
-	LabelIPPoolOwnerApplication    = AnnotationPre + "/owner-application"
 	LabelIPPoolOwnerApplicationUID = AnnotationPre + "/owner-application-uid"
-	LabelIPPoolInterface           = AnnotationPre + "/interface"
-	LabelIPPoolIPVersion           = AnnotationPre + "/ip-version"
-	LabelValueIPVersionV4          = "IPv4"
-	LabelValueIPVersionV6          = "IPv6"
+	LabelIPPoolReclaimIPPool       = AnnoSpiderSubnetReclaimIPPool
 
 	LabelSubnetCIDR = AnnotationPre + "/subnet-cidr"
 	LabelIPPoolCIDR = AnnotationPre + "/ippool-cidr"
-
-	// auto pool special pod affinity matchLabels key
-	AutoPoolPodAffinityAppPrefix     = AnnotationPre
-	AutoPoolPodAffinityAppAPIGroup   = AutoPoolPodAffinityAppPrefix + "/app-api-group"
-	AutoPoolPodAffinityAppAPIVersion = AutoPoolPodAffinityAppPrefix + "/app-api-version"
-	AutoPoolPodAffinityAppKind       = AutoPoolPodAffinityAppPrefix + "/app-kind"
-	AutoPoolPodAffinityAppNS         = AutoPoolPodAffinityAppPrefix + "/app-namespace"
-	AutoPoolPodAffinityAppName       = AutoPoolPodAffinityAppPrefix + "/app-name"
 )
 
 const (
diff --git a/pkg/gcmanager/gc_manager.go b/pkg/gcmanager/gc_manager.go
index ac9f5bd13..b4ff23718 100644
--- a/pkg/gcmanager/gc_manager.go
+++ b/pkg/gcmanager/gc_manager.go
@@ -177,6 +177,7 @@ func (s *SpiderGC) Health() bool {
 	defer cancelFunc()
 
 	if s.leader.IsElected() {
+		logger.Debug("try to check pod informer cache sync")
 		if s.informerFactory == nil {
 			logger.Warn("the IP-GC manager pod informer is not ready")
 			return false
diff --git a/pkg/ipam/allocate.go b/pkg/ipam/allocate.go
index 67a4388d9..5377d4ae6 100644
--- a/pkg/ipam/allocate.go
+++ b/pkg/ipam/allocate.go
@@ -18,7 +18,6 @@ import (
 
 	"github.com/spidernet-io/spiderpool/api/v1/agent/models"
 	"github.com/spidernet-io/spiderpool/pkg/constant"
-	"github.com/spidernet-io/spiderpool/pkg/ippoolmanager"
 	spiderpoolv2beta1 "github.com/spidernet-io/spiderpool/pkg/k8s/apis/spiderpool.spidernet.io/v2beta1"
 	"github.com/spidernet-io/spiderpool/pkg/logutils"
 	"github.com/spidernet-io/spiderpool/pkg/metric"
@@ -262,7 +261,7 @@ func (i *ipam) genToBeAllocatedSet(ctx context.Context, addArgs *models.IpamAddA
 
 	logger.Debug("Filter out IPPool candidates")
 	for _, t := range preliminary {
-		if err := i.filterPoolCandidates(ctx, t, pod, podController); err != nil {
+		if err := i.filterPoolCandidates(ctx, t, pod); err != nil {
 			return nil, err
 		}
 	}
@@ -410,7 +409,7 @@ func (i *ipam) precheckPoolCandidates(ctx context.Context, t *ToBeAllocated) err
 	return nil
 }
 
-func (i *ipam) filterPoolCandidates(ctx context.Context, t *ToBeAllocated, pod *corev1.Pod, podTopController types.PodTopController) error {
+func (i *ipam) filterPoolCandidates(ctx context.Context, t *ToBeAllocated, pod *corev1.Pod) error {
 	logger := logutils.FromContext(ctx)
 
 	for _, c := range t.PoolCandidates {
@@ -420,7 +419,7 @@ func (i *ipam) filterPoolCandidates(ctx context.Context, t *ToBeAllocated, pod *
 		var errs []error
 		for j := 0; j < len(c.Pools); j++ {
 			pool := c.Pools[j]
-			if err := i.selectByPod(ctx, c.IPVersion, c.PToIPPool[pool], pod, podTopController); err != nil {
+			if err := i.selectByPod(ctx, c.IPVersion, c.PToIPPool[pool], pod); err != nil {
 				logger.Sugar().Warnf("IPPool %s is filtered by Pod: %v", pool, err)
 				errs = append(errs, err)
 
@@ -438,7 +437,7 @@ func (i *ipam) filterPoolCandidates(ctx context.Context, t *ToBeAllocated, pod *
 	return nil
 }
 
-func (i *ipam) selectByPod(ctx context.Context, version types.IPVersion, ipPool *spiderpoolv2beta1.SpiderIPPool, pod *corev1.Pod, podTopController types.PodTopController) error {
+func (i *ipam) selectByPod(ctx context.Context, version types.IPVersion, ipPool *spiderpoolv2beta1.SpiderIPPool, pod *corev1.Pod) error {
 	if ipPool.DeletionTimestamp != nil {
 		return fmt.Errorf("terminating IPPool %s", ipPool.Name)
 	}
@@ -480,14 +479,6 @@ func (i *ipam) selectByPod(ctx context.Context, version types.IPVersion, ipPool
 	}
 
 	if ipPool.Spec.PodAffinity != nil {
-		if ippoolmanager.IsAutoCreatedIPPool(ipPool) {
-			if !ippoolmanager.IsMatchAutoPoolAffinity(ipPool.Spec.PodAffinity, podTopController) {
-				return fmt.Errorf("unmatched Pod annifity of auto-created IPool %s", ipPool.Name)
-			}
-
-			return nil
-		}
-
 		selector, err := metav1.LabelSelectorAsSelector(ipPool.Spec.PodAffinity)
 		if err != nil {
 			return err
diff --git a/pkg/ipam/pool_selections.go b/pkg/ipam/pool_selections.go
index 1ac31a631..72785bfee 100644
--- a/pkg/ipam/pool_selections.go
+++ b/pkg/ipam/pool_selections.go
@@ -6,6 +6,7 @@ package ipam
 import (
 	"context"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"strconv"
 	"sync"
@@ -22,6 +23,7 @@ import (
 	"github.com/spidernet-io/spiderpool/pkg/constant"
 	spiderpoolv2beta1 "github.com/spidernet-io/spiderpool/pkg/k8s/apis/spiderpool.spidernet.io/v2beta1"
 	"github.com/spidernet-io/spiderpool/pkg/logutils"
+	"github.com/spidernet-io/spiderpool/pkg/metric"
 	"github.com/spidernet-io/spiderpool/pkg/namespacemanager"
 	"github.com/spidernet-io/spiderpool/pkg/types"
 )
@@ -117,13 +119,79 @@ func (i *ipam) getPoolFromSubnetAnno(ctx context.Context, pod *corev1.Pod, nic s
 		CleanGateway: cleanGateway,
 	}
 
-	// This only serves for third party controller application, because we'll create or scale the auto-created IPPool here.
+	// This only serves for orphan pod or third party controller application, because we'll create or scale the auto-created IPPool here.
 	// For those kubernetes applications(such as deployment and replicaset), the spiderpool-controller will create or scale the auto-created IPPool asynchronously.
 	poolIPNum, err := getAutoPoolIPNumber(pod, podController)
 	if nil != err {
 		return nil, err
 	}
 
+	// This function will find the IPPool with the assembled IPPool name
+	// If the application is an orphan pod and do not find any IPPool, it will return immediately to inform you to create IPPool.
+	findSubnetIPPool := func(subnetName string, ipVersion types.IPVersion) (*spiderpoolv2beta1.SpiderIPPool, error) {
+		poolName := subnetmanagercontrollers.SubnetPoolName(podController.Name, ipVersion, nic, podController.UID)
+		var isRetried bool
+		defer func() {
+			if isRetried {
+				metric.AutoPoolWaitedForAvailableCounts.Add(ctx, 1)
+			}
+		}()
+
+		var pool *spiderpoolv2beta1.SpiderIPPool
+		var err error
+		for j := 1; j <= i.config.OperationRetries; j++ {
+			if j > 1 {
+				isRetried = true
+			}
+			// third-party controller applications
+			if !slices.Contains(constant.K8sAPIVersions, podController.APIVersion) || !slices.Contains(constant.K8sKinds, podController.Kind) {
+				pool, err = i.applyThirdControllerAutoPool(ctx, subnetName, poolName, podController, types.AutoPoolProperty{
+					DesiredIPNumber: poolIPNum,
+					IPVersion:       ipVersion,
+					IsReclaimIPPool: subnetAnnoConfig.ReclaimIPPool,
+					IfName:          nic,
+					PodSelector:     nil,
+				})
+			} else {
+				pool, err = i.ipPoolManager.GetIPPoolByName(ctx, poolName, constant.UseCache)
+			}
+
+			if nil != err {
+				if apierrors.IsNotFound(err) {
+					logger.Sugar().Warnf("fetch SubnetIPPool %d times: no '%s' IPPool retrieved from SpiderSubnet '%s', wait for a second and get a retry", j, poolName, subnetName)
+					time.Sleep(i.config.OperationGapDuration)
+					continue
+				}
+
+				if apierrors.IsConflict(err) || apierrors.IsAlreadyExists(err) || errors.Is(err, constant.ErrFreeIPsNotEnough) {
+					logger.Sugar().Warnf("fetch SubnetIPPool %d times: apply auto-created IPPool conflict: %v", j, err)
+					time.Sleep(i.config.OperationGapDuration)
+					continue
+				}
+
+				// we should just return error directly if we meet other errors
+				return nil, err
+			}
+
+			// found
+			logger.Sugar().Debugf("found SpiderSubnet '%s' IPPool '%v' ", subnetName, pool)
+
+			// TODO(Icarus9913): If shrink failed, it'll cost the whole loop
+			// we fetched Auto-created IPPool but it doesn't have any IPs, just wait for a while and let the IPPool informer to allocate IPs for it
+			if !isPoolIPsDesired(pool, poolIPNum) {
+				logger.Sugar().Warnf("fetch SubnetIPPool %d times: retrieved IPPool '%s' but doesn't have the desiredIPNumber IPs, wait for a second and get a retry", j, poolName)
+				time.Sleep(i.config.OperationGapDuration)
+				continue
+			}
+			break
+		}
+
+		if err != nil {
+			return nil, fmt.Errorf("auto-created IPPool '%s' is not available, please check it whether exists or has the desiredIPNumber IPs: %v", poolName, err)
+		}
+		return pool, nil
+	}
+
 	var v4PoolCandidate, v6PoolCandidate *spiderpoolv2beta1.SpiderIPPool
 	var errV4, errV6 error
 	var wg sync.WaitGroup
@@ -134,12 +202,7 @@ func (i *ipam) getPoolFromSubnetAnno(ctx context.Context, pod *corev1.Pod, nic s
 		go func() {
 			defer wg.Done()
 
-			if !slices.Contains(constant.K8sAPIVersions, podController.APIVersion) || !slices.Contains(constant.K8sKinds, podController.Kind) {
-				v4PoolCandidate, errV4 = i.applyThirdControllerAutoPool(ctx, subnetItem.IPv4[0], constant.IPv4, nic, poolIPNum, podController)
-			} else {
-				v4PoolCandidate, errV4 = i.findAppAutoPool(ctx, subnetItem.IPv4[0], nic, constant.LabelValueIPVersionV4, poolIPNum, podController)
-			}
-
+			v4PoolCandidate, errV4 = findSubnetIPPool(subnetItem.IPv4[0], constant.IPv4)
 			if nil != errV4 {
 				return
 			}
@@ -152,12 +215,7 @@ func (i *ipam) getPoolFromSubnetAnno(ctx context.Context, pod *corev1.Pod, nic s
 		go func() {
 			defer wg.Done()
 
-			if !slices.Contains(constant.K8sAPIVersions, podController.APIVersion) || !slices.Contains(constant.K8sKinds, podController.Kind) {
-				v6PoolCandidate, errV6 = i.applyThirdControllerAutoPool(ctx, subnetItem.IPv6[0], constant.IPv6, nic, poolIPNum, podController)
-			} else {
-				v6PoolCandidate, errV6 = i.findAppAutoPool(ctx, subnetItem.IPv6[0], nic, constant.LabelValueIPVersionV6, poolIPNum, podController)
-			}
-
+			v6PoolCandidate, errV6 = findSubnetIPPool(subnetItem.IPv6[0], constant.IPv6)
 			if nil != errV6 {
 				return
 			}
@@ -190,111 +248,27 @@ func (i *ipam) getPoolFromSubnetAnno(ctx context.Context, pod *corev1.Pod, nic s
 	return result, nil
 }
 
-// findAppAutoPool only fetches kubernetes basic controller(like Deployment, StatefulSet etc...) corresponding auto-created IPPools.
-func (i *ipam) findAppAutoPool(ctx context.Context, subnetName, ifName, labelIPPoolIPVersionValue string, desiredIPNumber int, podController types.PodTopController) (*spiderpoolv2beta1.SpiderIPPool, error) {
-	log := logutils.FromContext(ctx)
-
-	var pool *spiderpoolv2beta1.SpiderIPPool
-	matchLabels := client.MatchingLabels{
-		constant.LabelIPPoolOwnerSpiderSubnet:   subnetName,
-		constant.LabelIPPoolOwnerApplication:    subnetmanagercontrollers.ApplicationNamespacedName(podController.AppNamespacedName),
-		constant.LabelIPPoolOwnerApplicationUID: string(podController.UID),
-		constant.LabelIPPoolInterface:           ifName,
-		constant.LabelIPPoolIPVersion:           labelIPPoolIPVersionValue,
-	}
-	for j := 1; j <= i.config.OperationRetries; j++ {
-		poolList, err := i.ipPoolManager.ListIPPools(ctx, constant.UseCache, matchLabels)
-		if nil != err {
-			return nil, fmt.Errorf("failed to get auto-created IPPoolList with labels '%v', error: %w", matchLabels, err)
-		}
-
-		if len(poolList.Items) == 0 {
-			log.Sugar().Warnf("fetch SubnetIPPool %d times: no IPPool retrieved from SpiderSubnet '%s' with matchLabel '%v', wait for a second and get a retry",
-				j, subnetName, matchLabels)
-			time.Sleep(i.config.OperationGapDuration)
-		} else if len(poolList.Items) == 1 {
-			pool = poolList.Items[0].DeepCopy()
-			log.Sugar().Debugf("found SpiderSubnet '%s' IPPool '%s' with matchLabel '%v'", subnetName, pool.Name, matchLabels)
-
-			// we fetched Auto-created IPPool but it doesn't have any IPs, just wait for a while and let the IPPool informer to allocate IPs for it
-			if !isPoolIPsDesired(pool, desiredIPNumber) {
-				log.Sugar().Warnf("fetch SubnetIPPool %d times: retrieved IPPool '%s' but doesn't have the desiredIPNumber IPs, wait for a second and get a retry", j, pool.Name)
-				time.Sleep(i.config.OperationGapDuration)
-				continue
+func (i *ipam) applyThirdControllerAutoPool(ctx context.Context, subnetName, poolName string, podController types.PodTopController, autoPoolProperty types.AutoPoolProperty) (*spiderpoolv2beta1.SpiderIPPool, error) {
+	tmpPool, err := i.ipPoolManager.GetIPPoolByName(ctx, poolName, constant.UseCache)
+	if nil != err {
+		if apierrors.IsNotFound(err) {
+			// create one auto-created IPPool for third-party controller application
+			tmpPool, err = i.subnetManager.ReconcileAutoIPPool(ctx, nil, subnetName, podController, autoPoolProperty)
+			if nil != err {
+				return nil, err
 			}
-			break
-		} else {
-			return nil, fmt.Errorf("it's invalid for '%s/%s/%s' corresponding SpiderSubnet '%s' owns multiple matchLables '%v' corresponding IPPools '%v' for one specify application",
-				podController.Kind, podController.Namespace, podController.Name, subnetName, matchLabels, poolList.Items)
+			return tmpPool, nil
 		}
-	}
-	if pool == nil {
-		return nil, fmt.Errorf("no matching auto-created IPPool candidate with matchLables '%v'", matchLabels)
-	}
-
-	return pool, nil
-}
 
-// applyThirdControllerAutoPool will fetch or reconcile third-party controller corresponding auto-created IPPools,
-// and the kubernetes basic controller like Deployment,StatefulSet etc... We'll reconcile their auto-created IPPools in spiderpool-controller component.
-func (i *ipam) applyThirdControllerAutoPool(ctx context.Context, subnetName string, ipVersion types.IPVersion, ifName string, desiredIPNumber int, podController types.PodTopController) (*spiderpoolv2beta1.SpiderIPPool, error) {
-	log := logutils.FromContext(ctx)
-
-	var labelIPPoolIPVersionValue string
-	if ipVersion == constant.IPv4 {
-		labelIPPoolIPVersionValue = constant.LabelValueIPVersionV4
-	} else {
-		labelIPPoolIPVersionValue = constant.LabelValueIPVersionV6
+		return nil, err
 	}
 
-	var pool *spiderpoolv2beta1.SpiderIPPool
-	matchLabels := client.MatchingLabels{
-		constant.LabelIPPoolOwnerSpiderSubnet: subnetName,
-		constant.LabelIPPoolOwnerApplication:  subnetmanagercontrollers.ApplicationNamespacedName(podController.AppNamespacedName),
-		constant.LabelIPPoolIPVersion:         labelIPPoolIPVersionValue,
-		constant.LabelIPPoolInterface:         ifName,
+	// check whether the auto IPPool need to scale its desiredIPNumber or not
+	pool, err := i.subnetManager.ReconcileAutoIPPool(ctx, tmpPool, subnetName, podController, autoPoolProperty)
+	if nil != err {
+		return nil, err
 	}
-	for j := 1; j <= i.config.OperationRetries; j++ {
-		poolList, err := i.ipPoolManager.ListIPPools(ctx, constant.UseCache, matchLabels)
-		if nil != err {
-			return nil, fmt.Errorf("failed to get third-party auto-created IPPoolList with matchLabels %v, error: %w", matchLabels, err)
-		}
-
-		if len(poolList.Items) == 0 {
-			pool = nil
-		} else {
-			for k := range poolList.Items {
-				labels := poolList.Items[k].GetLabels()
-				if labels[constant.LabelIPPoolReclaimIPPool] == constant.True && labels[constant.LabelIPPoolOwnerApplicationUID] != string(podController.UID) {
-					log.Sugar().Debugf("found the previous same app auto-created IPPool %s", poolList.Items[k].Name)
-					continue
-				}
 
-				pool = poolList.Items[k].DeepCopy()
-				log.Sugar().Infof("found reuse app auto-created IPPool %s", pool.Name)
-				break
-			}
-		}
-
-		pool, err = i.subnetManager.ReconcileAutoIPPool(ctx, pool, subnetName, podController, types.AutoPoolProperty{
-			DesiredIPNumber: desiredIPNumber,
-			IPVersion:       ipVersion,
-			IsReclaimIPPool: false,
-			IfName:          ifName,
-		})
-		if nil != err {
-			if apierrors.IsConflict(err) || apierrors.IsAlreadyExists(err) {
-				log.Sugar().Warnf("fetch SubnetIPPool %d times: apply auto-created IPPool conflict: %v", j, err)
-				time.Sleep(i.config.OperationGapDuration)
-				continue
-			}
-			return nil, fmt.Errorf("failed to check SpiderSubnet %s third-party controller auto-created IPPool whether need to be scaled: %w", subnetName, err)
-		}
-		break
-	}
-	if pool == nil {
-		return nil, fmt.Errorf("no matching third-party controller auto-created IPPool candidate with matchLables '%v'", matchLabels)
-	}
 	return pool, nil
 }
 
diff --git a/pkg/ippoolmanager/ippool_informer.go b/pkg/ippoolmanager/ippool_informer.go
index 1ad465a6e..28dff3c92 100644
--- a/pkg/ippoolmanager/ippool_informer.go
+++ b/pkg/ippoolmanager/ippool_informer.go
@@ -366,10 +366,10 @@ func (ic *IPPoolController) cleanAutoIPPoolLegacy(ctx context.Context, pool *spi
 
 	if pool.Status.AllocatedIPs == nil {
 		// unpack the IPPool corresponding application type,namespace and name
-		appNamespacedNameStr := poolLabels[constant.LabelIPPoolOwnerApplication]
+		appNamespacedNameStr := pool.Annotations[constant.AnnoSpiderSubnetPoolApp]
 		appNamespacedName, isMatch := applicationinformers.ParseApplicationNamespacedName(appNamespacedNameStr)
 		if !isMatch {
-			return fmt.Errorf("%w: invalid IPPool label '%s' value '%s'", constant.ErrWrongInput, constant.LabelIPPoolOwnerApplication, appNamespacedNameStr)
+			return fmt.Errorf("%w: invalid IPPool annotation '%s' value '%s'", constant.ErrWrongInput, constant.AnnoSpiderSubnetPoolApp, appNamespacedNameStr)
 		}
 
 		enableDelete := false
diff --git a/pkg/ippoolmanager/utils.go b/pkg/ippoolmanager/utils.go
index 85c470def..982bd1df6 100644
--- a/pkg/ippoolmanager/utils.go
+++ b/pkg/ippoolmanager/utils.go
@@ -4,64 +4,13 @@
 package ippoolmanager
 
 import (
-	"strings"
-
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/labels"
-
 	"github.com/spidernet-io/spiderpool/pkg/constant"
 	spiderpoolv2beta1 "github.com/spidernet-io/spiderpool/pkg/k8s/apis/spiderpool.spidernet.io/v2beta1"
-	"github.com/spidernet-io/spiderpool/pkg/types"
 )
 
 func IsAutoCreatedIPPool(pool *spiderpoolv2beta1.SpiderIPPool) bool {
-	// only the auto-created IPPool owns the annotation "ipam.spidernet.io/owner-application"
-	poolLabels := pool.GetLabels()
-	_, ok := poolLabels[constant.LabelIPPoolOwnerApplication]
+	// only the auto-created IPPool owns the annotation "ipam.spidernet.io/application"
+	poolAnno := pool.GetAnnotations()
+	_, ok := poolAnno[constant.AnnoSpiderSubnetPoolApp]
 	return ok
 }
-
-func NewAutoPoolPodAffinity(podTopController types.PodTopController) *metav1.LabelSelector {
-	var group, version string
-
-	first, second, hasGroup := strings.Cut(podTopController.APIVersion, "/")
-	if hasGroup {
-		group = first
-		version = second
-	} else {
-		version = first
-	}
-
-	set := labels.Set{
-		constant.AutoPoolPodAffinityAppAPIGroup:   group,
-		constant.AutoPoolPodAffinityAppAPIVersion: version,
-		constant.AutoPoolPodAffinityAppKind:       podTopController.Kind,
-		constant.AutoPoolPodAffinityAppNS:         podTopController.Namespace,
-		constant.AutoPoolPodAffinityAppName:       podTopController.Name,
-	}
-
-	return metav1.SetAsLabelSelector(set)
-}
-
-func IsMatchAutoPoolAffinity(podAffinity *metav1.LabelSelector, podTopController types.PodTopController) bool {
-	if podAffinity == nil {
-		return false
-	}
-
-	group, version, _ := strings.Cut(podTopController.APIVersion, "/")
-	tmpSet := labels.Set{
-		constant.AutoPoolPodAffinityAppAPIGroup:   group,
-		constant.AutoPoolPodAffinityAppAPIVersion: version,
-		constant.AutoPoolPodAffinityAppKind:       podTopController.Kind,
-		constant.AutoPoolPodAffinityAppNS:         podTopController.Namespace,
-		constant.AutoPoolPodAffinityAppName:       podTopController.Name,
-	}
-
-	for k, v := range tmpSet {
-		if podAffinity.MatchLabels[k] != v {
-			return false
-		}
-	}
-
-	return true
-}
diff --git a/pkg/k8s/apis/spiderpool.spidernet.io/v2beta1/spiderippool_types.go b/pkg/k8s/apis/spiderpool.spidernet.io/v2beta1/spiderippool_types.go
index 00c353cb7..df4f8deb0 100644
--- a/pkg/k8s/apis/spiderpool.spidernet.io/v2beta1/spiderippool_types.go
+++ b/pkg/k8s/apis/spiderpool.spidernet.io/v2beta1/spiderippool_types.go
@@ -90,7 +90,6 @@ type PoolIPAllocation struct {
 // +kubebuilder:printcolumn:JSONPath=".status.totalIPCount",description="totalIPCount",name="TOTAL-IP-COUNT",type=integer
 // +kubebuilder:printcolumn:JSONPath=".spec.default",description="default",name="DEFAULT",type=boolean
 // +kubebuilder:printcolumn:JSONPath=".spec.disable",description="disable",name="DISABLE",type=boolean
-// +kubebuilder:printcolumn:JSONPath=`.spec.podAffinity.matchLabels['ipam\.spidernet\.io/app\-namespace']`,description="AppNamespace",name="APP-NAMESPACE",type=string,priority=10
 // +kubebuilder:object:root=true
 // +kubebuilder:subresource:status
 // +genclient
diff --git a/pkg/subnetmanager/subnet_manager.go b/pkg/subnetmanager/subnet_manager.go
index 783f8bca1..1cbcbec1f 100644
--- a/pkg/subnetmanager/subnet_manager.go
+++ b/pkg/subnetmanager/subnet_manager.go
@@ -21,7 +21,6 @@ import (
 	"github.com/spidernet-io/spiderpool/pkg/constant"
 	"github.com/spidernet-io/spiderpool/pkg/event"
 	spiderpoolip "github.com/spidernet-io/spiderpool/pkg/ip"
-	"github.com/spidernet-io/spiderpool/pkg/ippoolmanager"
 	spiderpoolv2beta1 "github.com/spidernet-io/spiderpool/pkg/k8s/apis/spiderpool.spidernet.io/v2beta1"
 	"github.com/spidernet-io/spiderpool/pkg/logutils"
 	"github.com/spidernet-io/spiderpool/pkg/reservedipmanager"
@@ -121,19 +120,14 @@ func (sm *subnetManager) ReconcileAutoIPPool(ctx context.Context, pool *spiderpo
 		if nil != err {
 			return nil, fmt.Errorf("%w: failed to parse IPPool %s Spec IPs %s: %v", constant.ErrWrongInput, pool.Name, pool.Spec.IPs, err)
 		}
-		if len(poolIPs) == autoPoolProperty.DesiredIPNumber && pool.Labels[constant.LabelIPPoolOwnerApplicationUID] == string(podController.UID) {
+		if len(poolIPs) == autoPoolProperty.DesiredIPNumber {
 			log.Sugar().Debugf("Auto-created IPPool %s matches the desired IP number %d, no need to reconcile", pool.Name, autoPoolProperty.DesiredIPNumber)
 			return pool, nil
 		}
-
-		// refresh the label "ipam.spidernet.io/owner-application-uid"
-		labels := pool.GetLabels()
-		labels[constant.LabelIPPoolOwnerApplicationUID] = string(podController.UID)
-		pool.SetLabels(labels)
 	} else {
 		pool = &spiderpoolv2beta1.SpiderIPPool{
 			ObjectMeta: metav1.ObjectMeta{
-				Name: applicationinformers.AutoPoolName(podController.Name, autoPoolProperty.IPVersion, autoPoolProperty.IfName, podController.UID),
+				Name: applicationinformers.SubnetPoolName(podController.Name, autoPoolProperty.IPVersion, autoPoolProperty.IfName, podController.UID),
 			},
 			Spec: spiderpoolv2beta1.IPPoolSpec{
 				IPVersion:   pointer.Int64(autoPoolProperty.IPVersion),
@@ -141,36 +135,30 @@ func (sm *subnetManager) ReconcileAutoIPPool(ctx context.Context, pool *spiderpo
 				Gateway:     subnet.Spec.Gateway,
 				Vlan:        subnet.Spec.Vlan,
 				Routes:      subnet.Spec.Routes,
-				PodAffinity: ippoolmanager.NewAutoPoolPodAffinity(podController),
+				PodAffinity: autoPoolProperty.PodSelector,
 			},
 		}
 
-		{
-			poolLabels := map[string]string{
-				constant.LabelIPPoolOwnerSpiderSubnet:   subnet.Name,
-				constant.LabelIPPoolOwnerApplication:    applicationinformers.ApplicationNamespacedName(podController.AppNamespacedName),
-				constant.LabelIPPoolOwnerApplicationUID: string(podController.UID),
-				constant.LabelIPPoolInterface:           autoPoolProperty.IfName,
-			}
-			// label IPPoolCIDR
-			cidrLabelValue, err := spiderpoolip.CIDRToLabelValue(*pool.Spec.IPVersion, pool.Spec.Subnet)
-			if nil != err {
-				return nil, fmt.Errorf("failed to parse '%s' when allocating empty Auto-created IPPool '%v'", pool.Spec.Subnet, pool)
-			}
-			poolLabels[constant.LabelIPPoolCIDR] = cidrLabelValue
-			if autoPoolProperty.IsReclaimIPPool {
-				poolLabels[constant.LabelIPPoolReclaimIPPool] = constant.True
-			} else {
-				poolLabels[constant.LabelIPPoolReclaimIPPool] = constant.False
-			}
-			// label IPVersion
-			if autoPoolProperty.IPVersion == constant.IPv4 {
-				poolLabels[constant.LabelIPPoolIPVersion] = constant.LabelValueIPVersionV4
-			} else {
-				poolLabels[constant.LabelIPPoolIPVersion] = constant.LabelValueIPVersionV6
-			}
-			pool.Labels = poolLabels
+		poolLabels := map[string]string{
+			constant.LabelIPPoolOwnerSpiderSubnet:   subnet.Name,
+			constant.LabelIPPoolOwnerApplicationUID: string(podController.UID),
+		}
+		poolAnno := map[string]string{
+			constant.AnnoSpiderSubnetPoolApp: applicationinformers.ApplicationNamespacedName(podController.AppNamespacedName),
+		}
+
+		cidrLabelValue, err := spiderpoolip.CIDRToLabelValue(*pool.Spec.IPVersion, pool.Spec.Subnet)
+		if nil != err {
+			return nil, fmt.Errorf("failed to parse '%s' when allocating empty Auto-created IPPool '%v'", pool.Spec.Subnet, pool)
+		}
+		poolLabels[constant.LabelIPPoolCIDR] = cidrLabelValue
+		if autoPoolProperty.IsReclaimIPPool {
+			poolLabels[constant.LabelIPPoolReclaimIPPool] = constant.True
+		} else {
+			poolLabels[constant.LabelIPPoolReclaimIPPool] = constant.False
 		}
+		pool.Labels = poolLabels
+		pool.Annotations = poolAnno
 
 		// set owner reference
 		err = ctrl.SetControllerReference(subnet, pool, sm.client.Scheme())
diff --git a/pkg/types/k8s.go b/pkg/types/k8s.go
index 737ae51d6..40bee386d 100644
--- a/pkg/types/k8s.go
+++ b/pkg/types/k8s.go
@@ -104,4 +104,5 @@ type AutoPoolProperty struct {
 	IPVersion       IPVersion
 	IsReclaimIPPool bool
 	IfName          string
+	PodSelector     *metav1.LabelSelector
 }
diff --git a/vendor/github.com/containernetworking/plugins/LICENSE b/vendor/github.com/containernetworking/plugins/LICENSE
deleted file mode 100644
index 8dada3eda..000000000
--- a/vendor/github.com/containernetworking/plugins/LICENSE
+++ /dev/null
@@ -1,201 +0,0 @@
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "{}"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright {yyyy} {name of copyright owner}
-
-   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.
diff --git a/vendor/github.com/containernetworking/plugins/pkg/ns/README.md b/vendor/github.com/containernetworking/plugins/pkg/ns/README.md
deleted file mode 100644
index 1e265c7a0..000000000
--- a/vendor/github.com/containernetworking/plugins/pkg/ns/README.md
+++ /dev/null
@@ -1,41 +0,0 @@
-### Namespaces, Threads, and Go
-On Linux each OS thread can have a different network namespace.  Go's thread scheduling model switches goroutines between OS threads based on OS thread load and whether the goroutine would block other goroutines.  This can result in a goroutine switching network namespaces without notice and lead to errors in your code.
-
-### Namespace Switching
-Switching namespaces with the `ns.Set()` method is not recommended without additional strategies to prevent unexpected namespace changes when your goroutines switch OS threads.
-
-Go provides the `runtime.LockOSThread()` function to ensure a specific goroutine executes on its current OS thread and prevents any other goroutine from running in that thread until the locked one exits.  Careful usage of `LockOSThread()` and goroutines can provide good control over which network namespace a given goroutine executes in.
-
-For example, you cannot rely on the `ns.Set()` namespace being the current namespace after the `Set()` call unless you do two things.  First, the goroutine calling `Set()` must have previously called `LockOSThread()`.  Second, you must ensure `runtime.UnlockOSThread()` is not called somewhere in-between.  You also cannot rely on the initial network namespace remaining the current network namespace if any other code in your program switches namespaces, unless you have already called `LockOSThread()` in that goroutine.  Note that `LockOSThread()` prevents the Go scheduler from optimally scheduling goroutines for best performance, so `LockOSThread()` should only be used in small, isolated goroutines that release the lock quickly.
-
-### Do() The Recommended Thing
-The `ns.Do()` method provides **partial** control over network namespaces for you by implementing these strategies. All code dependent on a particular network namespace (including the root namespace) should be wrapped in the `ns.Do()` method to ensure the correct namespace is selected for the duration of your code.  For example:
-
-```go
-err = targetNs.Do(func(hostNs ns.NetNS) error {
-	dummy := &netlink.Dummy{
-		LinkAttrs: netlink.LinkAttrs{
-			Name: "dummy0",
-		},
-	}
-	return netlink.LinkAdd(dummy)
-})
-```
-
-Note this requirement to wrap every network call is very onerous - any libraries you call might call out to network services such as DNS, and all such calls need to be protected after you call `ns.Do()`. All goroutines spawned from within the `ns.Do` will not inherit the new namespace. The CNI plugins all exit very soon after calling `ns.Do()` which helps to minimize the problem.
-
-When a new thread is spawned in Linux, it inherits the namespace of its parent. In versions of go **prior to 1.10**, if the runtime spawns a new OS thread, it picks the parent randomly. If the chosen parent thread has been moved to a new namespace (even temporarily), the new OS thread will be permanently "stuck in the wrong namespace", and goroutines will non-deterministically switch namespaces as they are rescheduled.
-
-In short, **there was no safe way to change network namespaces, even temporarily, from within a long-lived, multithreaded Go process**. If you wish to do this, you must use go 1.10 or greater. 
-
-
-### Creating network namespaces
-Earlier versions of this library managed namespace creation, but as CNI does not actually utilize this feature (and it was essentially unmaintained), it was removed. If you're writing a container runtime, you should implement namespace management yourself. However, there are some gotchas when doing so, especially around handling `/var/run/netns`. A reasonably correct reference implementation, borrowed from `rkt`, can be found in `pkg/testutils/netns_linux.go` if you're in need of a source of inspiration.
-
-
-### Further Reading
- - https://github.com/golang/go/wiki/LockOSThread
- - http://morsmachine.dk/go-scheduler
- - https://github.com/containernetworking/cni/issues/262
- - https://golang.org/pkg/runtime/
- - https://www.weave.works/blog/linux-namespaces-and-go-don-t-mix
diff --git a/vendor/github.com/containernetworking/plugins/pkg/ns/ns_linux.go b/vendor/github.com/containernetworking/plugins/pkg/ns/ns_linux.go
deleted file mode 100644
index f260f2813..000000000
--- a/vendor/github.com/containernetworking/plugins/pkg/ns/ns_linux.go
+++ /dev/null
@@ -1,234 +0,0 @@
-// Copyright 2015-2017 CNI 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 ns
-
-import (
-	"fmt"
-	"os"
-	"runtime"
-	"sync"
-	"syscall"
-
-	"golang.org/x/sys/unix"
-)
-
-// Returns an object representing the current OS thread's network namespace
-func GetCurrentNS() (NetNS, error) {
-	// Lock the thread in case other goroutine executes in it and changes its
-	// network namespace after getCurrentThreadNetNSPath(), otherwise it might
-	// return an unexpected network namespace.
-	runtime.LockOSThread()
-	defer runtime.UnlockOSThread()
-	return GetNS(getCurrentThreadNetNSPath())
-}
-
-func getCurrentThreadNetNSPath() string {
-	// /proc/self/ns/net returns the namespace of the main thread, not
-	// of whatever thread this goroutine is running on.  Make sure we
-	// use the thread's net namespace since the thread is switching around
-	return fmt.Sprintf("/proc/%d/task/%d/ns/net", os.Getpid(), unix.Gettid())
-}
-
-func (ns *netNS) Close() error {
-	if err := ns.errorIfClosed(); err != nil {
-		return err
-	}
-
-	if err := ns.file.Close(); err != nil {
-		return fmt.Errorf("Failed to close %q: %v", ns.file.Name(), err)
-	}
-	ns.closed = true
-
-	return nil
-}
-
-func (ns *netNS) Set() error {
-	if err := ns.errorIfClosed(); err != nil {
-		return err
-	}
-
-	if err := unix.Setns(int(ns.Fd()), unix.CLONE_NEWNET); err != nil {
-		return fmt.Errorf("Error switching to ns %v: %v", ns.file.Name(), err)
-	}
-
-	return nil
-}
-
-type NetNS interface {
-	// Executes the passed closure in this object's network namespace,
-	// attempting to restore the original namespace before returning.
-	// However, since each OS thread can have a different network namespace,
-	// and Go's thread scheduling is highly variable, callers cannot
-	// guarantee any specific namespace is set unless operations that
-	// require that namespace are wrapped with Do().  Also, no code called
-	// from Do() should call runtime.UnlockOSThread(), or the risk
-	// of executing code in an incorrect namespace will be greater.  See
-	// https://github.com/golang/go/wiki/LockOSThread for further details.
-	Do(toRun func(NetNS) error) error
-
-	// Sets the current network namespace to this object's network namespace.
-	// Note that since Go's thread scheduling is highly variable, callers
-	// cannot guarantee the requested namespace will be the current namespace
-	// after this function is called; to ensure this wrap operations that
-	// require the namespace with Do() instead.
-	Set() error
-
-	// Returns the filesystem path representing this object's network namespace
-	Path() string
-
-	// Returns a file descriptor representing this object's network namespace
-	Fd() uintptr
-
-	// Cleans up this instance of the network namespace; if this instance
-	// is the last user the namespace will be destroyed
-	Close() error
-}
-
-type netNS struct {
-	file   *os.File
-	closed bool
-}
-
-// netNS implements the NetNS interface
-var _ NetNS = &netNS{}
-
-const (
-	// https://github.com/torvalds/linux/blob/master/include/uapi/linux/magic.h
-	NSFS_MAGIC   = unix.NSFS_MAGIC
-	PROCFS_MAGIC = unix.PROC_SUPER_MAGIC
-)
-
-type NSPathNotExistErr struct{ msg string }
-
-func (e NSPathNotExistErr) Error() string { return e.msg }
-
-type NSPathNotNSErr struct{ msg string }
-
-func (e NSPathNotNSErr) Error() string { return e.msg }
-
-func IsNSorErr(nspath string) error {
-	stat := syscall.Statfs_t{}
-	if err := syscall.Statfs(nspath, &stat); err != nil {
-		if os.IsNotExist(err) {
-			err = NSPathNotExistErr{msg: fmt.Sprintf("failed to Statfs %q: %v", nspath, err)}
-		} else {
-			err = fmt.Errorf("failed to Statfs %q: %v", nspath, err)
-		}
-		return err
-	}
-
-	switch stat.Type {
-	case PROCFS_MAGIC, NSFS_MAGIC:
-		return nil
-	default:
-		return NSPathNotNSErr{msg: fmt.Sprintf("unknown FS magic on %q: %x", nspath, stat.Type)}
-	}
-}
-
-// Returns an object representing the namespace referred to by @path
-func GetNS(nspath string) (NetNS, error) {
-	err := IsNSorErr(nspath)
-	if err != nil {
-		return nil, err
-	}
-
-	fd, err := os.Open(nspath)
-	if err != nil {
-		return nil, err
-	}
-
-	return &netNS{file: fd}, nil
-}
-
-func (ns *netNS) Path() string {
-	return ns.file.Name()
-}
-
-func (ns *netNS) Fd() uintptr {
-	return ns.file.Fd()
-}
-
-func (ns *netNS) errorIfClosed() error {
-	if ns.closed {
-		return fmt.Errorf("%q has already been closed", ns.file.Name())
-	}
-	return nil
-}
-
-func (ns *netNS) Do(toRun func(NetNS) error) error {
-	if err := ns.errorIfClosed(); err != nil {
-		return err
-	}
-
-	containedCall := func(hostNS NetNS) error {
-		threadNS, err := GetCurrentNS()
-		if err != nil {
-			return fmt.Errorf("failed to open current netns: %v", err)
-		}
-		defer threadNS.Close()
-
-		// switch to target namespace
-		if err = ns.Set(); err != nil {
-			return fmt.Errorf("error switching to ns %v: %v", ns.file.Name(), err)
-		}
-		defer func() {
-			err := threadNS.Set() // switch back
-			if err == nil {
-				// Unlock the current thread only when we successfully switched back
-				// to the original namespace; otherwise leave the thread locked which
-				// will force the runtime to scrap the current thread, that is maybe
-				// not as optimal but at least always safe to do.
-				runtime.UnlockOSThread()
-			}
-		}()
-
-		return toRun(hostNS)
-	}
-
-	// save a handle to current network namespace
-	hostNS, err := GetCurrentNS()
-	if err != nil {
-		return fmt.Errorf("Failed to open current namespace: %v", err)
-	}
-	defer hostNS.Close()
-
-	var wg sync.WaitGroup
-	wg.Add(1)
-
-	// Start the callback in a new green thread so that if we later fail
-	// to switch the namespace back to the original one, we can safely
-	// leave the thread locked to die without a risk of the current thread
-	// left lingering with incorrect namespace.
-	var innerError error
-	go func() {
-		defer wg.Done()
-		runtime.LockOSThread()
-		innerError = containedCall(hostNS)
-	}()
-	wg.Wait()
-
-	return innerError
-}
-
-// WithNetNSPath executes the passed closure under the given network
-// namespace, restoring the original namespace afterwards.
-func WithNetNSPath(nspath string, toRun func(NetNS) error) error {
-	ns, err := GetNS(nspath)
-	if err != nil {
-		return err
-	}
-	defer ns.Close()
-	return ns.Do(toRun)
-}
diff --git a/vendor/github.com/onsi/gomega/ghttp/handlers.go b/vendor/github.com/onsi/gomega/ghttp/handlers.go
deleted file mode 100644
index a80b27ddf..000000000
--- a/vendor/github.com/onsi/gomega/ghttp/handlers.go
+++ /dev/null
@@ -1,403 +0,0 @@
-// untested sections: 3
-
-package ghttp
-
-import (
-	"encoding/base64"
-	"encoding/json"
-	"fmt"
-	"net/http"
-	"net/url"
-	"reflect"
-	"strings"
-
-	"github.com/golang/protobuf/proto"
-	"github.com/onsi/gomega"
-	. "github.com/onsi/gomega"
-	"github.com/onsi/gomega/internal/gutil"
-	"github.com/onsi/gomega/types"
-)
-
-type GHTTPWithGomega struct {
-	gomega Gomega
-}
-
-func NewGHTTPWithGomega(gomega Gomega) *GHTTPWithGomega {
-	return &GHTTPWithGomega{
-		gomega: gomega,
-	}
-}
-
-//CombineHandler takes variadic list of handlers and produces one handler
-//that calls each handler in order.
-func CombineHandlers(handlers ...http.HandlerFunc) http.HandlerFunc {
-	return func(w http.ResponseWriter, req *http.Request) {
-		for _, handler := range handlers {
-			handler(w, req)
-		}
-	}
-}
-
-//VerifyRequest returns a handler that verifies that a request uses the specified method to connect to the specified path
-//You may also pass in an optional rawQuery string which is tested against the request's `req.URL.RawQuery`
-//
-//For path, you may pass in a string, in which case strict equality will be applied
-//Alternatively you can pass in a matcher (ContainSubstring("/foo") and MatchRegexp("/foo/[a-f0-9]+") for example)
-func (g GHTTPWithGomega) VerifyRequest(method string, path interface{}, rawQuery ...string) http.HandlerFunc {
-	return func(w http.ResponseWriter, req *http.Request) {
-		g.gomega.Expect(req.Method).Should(Equal(method), "Method mismatch")
-		switch p := path.(type) {
-		case types.GomegaMatcher:
-			g.gomega.Expect(req.URL.Path).Should(p, "Path mismatch")
-		default:
-			g.gomega.Expect(req.URL.Path).Should(Equal(path), "Path mismatch")
-		}
-		if len(rawQuery) > 0 {
-			values, err := url.ParseQuery(rawQuery[0])
-			g.gomega.Expect(err).ShouldNot(HaveOccurred(), "Expected RawQuery is malformed")
-
-			g.gomega.Expect(req.URL.Query()).Should(Equal(values), "RawQuery mismatch")
-		}
-	}
-}
-
-//VerifyContentType returns a handler that verifies that a request has a Content-Type header set to the
-//specified value
-func (g GHTTPWithGomega) VerifyContentType(contentType string) http.HandlerFunc {
-	return func(w http.ResponseWriter, req *http.Request) {
-		g.gomega.Expect(req.Header.Get("Content-Type")).Should(Equal(contentType))
-	}
-}
-
-//VerifyMimeType returns a handler that verifies that a request has a specified mime type set
-//in Content-Type header
-func (g GHTTPWithGomega) VerifyMimeType(mimeType string) http.HandlerFunc {
-	return func(w http.ResponseWriter, req *http.Request) {
-		g.gomega.Expect(strings.Split(req.Header.Get("Content-Type"), ";")[0]).Should(Equal(mimeType))
-	}
-}
-
-//VerifyBasicAuth returns a handler that verifies the request contains a BasicAuth Authorization header
-//matching the passed in username and password
-func (g GHTTPWithGomega) VerifyBasicAuth(username string, password string) http.HandlerFunc {
-	return func(w http.ResponseWriter, req *http.Request) {
-		auth := req.Header.Get("Authorization")
-		g.gomega.Expect(auth).ShouldNot(Equal(""), "Authorization header must be specified")
-
-		decoded, err := base64.StdEncoding.DecodeString(auth[6:])
-		g.gomega.Expect(err).ShouldNot(HaveOccurred())
-
-		g.gomega.Expect(string(decoded)).Should(Equal(fmt.Sprintf("%s:%s", username, password)), "Authorization mismatch")
-	}
-}
-
-//VerifyHeader returns a handler that verifies the request contains the passed in headers.
-//The passed in header keys are first canonicalized via http.CanonicalHeaderKey.
-//
-//The request must contain *all* the passed in headers, but it is allowed to have additional headers
-//beyond the passed in set.
-func (g GHTTPWithGomega) VerifyHeader(header http.Header) http.HandlerFunc {
-	return func(w http.ResponseWriter, req *http.Request) {
-		for key, values := range header {
-			key = http.CanonicalHeaderKey(key)
-			g.gomega.Expect(req.Header[key]).Should(Equal(values), "Header mismatch for key: %s", key)
-		}
-	}
-}
-
-//VerifyHeaderKV returns a handler that verifies the request contains a header matching the passed in key and values
-//(recall that a `http.Header` is a mapping from string (key) to []string (values))
-//It is a convenience wrapper around `VerifyHeader` that allows you to avoid having to create an `http.Header` object.
-func (g GHTTPWithGomega) VerifyHeaderKV(key string, values ...string) http.HandlerFunc {
-	return g.VerifyHeader(http.Header{key: values})
-}
-
-//VerifyBody returns a handler that verifies that the body of the request matches the passed in byte array.
-//It does this using Equal().
-func (g GHTTPWithGomega) VerifyBody(expectedBody []byte) http.HandlerFunc {
-	return CombineHandlers(
-		func(w http.ResponseWriter, req *http.Request) {
-			body, err := gutil.ReadAll(req.Body)
-			req.Body.Close()
-			g.gomega.Expect(err).ShouldNot(HaveOccurred())
-			g.gomega.Expect(body).Should(Equal(expectedBody), "Body Mismatch")
-		},
-	)
-}
-
-//VerifyJSON returns a handler that verifies that the body of the request is a valid JSON representation
-//matching the passed in JSON string.  It does this using Gomega's MatchJSON method
-//
-//VerifyJSON also verifies that the request's content type is application/json
-func (g GHTTPWithGomega) VerifyJSON(expectedJSON string) http.HandlerFunc {
-	return CombineHandlers(
-		g.VerifyMimeType("application/json"),
-		func(w http.ResponseWriter, req *http.Request) {
-			body, err := gutil.ReadAll(req.Body)
-			req.Body.Close()
-			g.gomega.Expect(err).ShouldNot(HaveOccurred())
-			g.gomega.Expect(body).Should(MatchJSON(expectedJSON), "JSON Mismatch")
-		},
-	)
-}
-
-//VerifyJSONRepresenting is similar to VerifyJSON.  Instead of taking a JSON string, however, it
-//takes an arbitrary JSON-encodable object and verifies that the requests's body is a JSON representation
-//that matches the object
-func (g GHTTPWithGomega) VerifyJSONRepresenting(object interface{}) http.HandlerFunc {
-	data, err := json.Marshal(object)
-	g.gomega.Expect(err).ShouldNot(HaveOccurred())
-	return CombineHandlers(
-		g.VerifyMimeType("application/json"),
-		g.VerifyJSON(string(data)),
-	)
-}
-
-//VerifyForm returns a handler that verifies a request contains the specified form values.
-//
-//The request must contain *all* of the specified values, but it is allowed to have additional
-//form values beyond the passed in set.
-func (g GHTTPWithGomega) VerifyForm(values url.Values) http.HandlerFunc {
-	return func(w http.ResponseWriter, r *http.Request) {
-		err := r.ParseForm()
-		g.gomega.Expect(err).ShouldNot(HaveOccurred())
-		for key, vals := range values {
-			g.gomega.Expect(r.Form[key]).Should(Equal(vals), "Form mismatch for key: %s", key)
-		}
-	}
-}
-
-//VerifyFormKV returns a handler that verifies a request contains a form key with the specified values.
-//
-//It is a convenience wrapper around `VerifyForm` that lets you avoid having to create a `url.Values` object.
-func (g GHTTPWithGomega) VerifyFormKV(key string, values ...string) http.HandlerFunc {
-	return g.VerifyForm(url.Values{key: values})
-}
-
-//VerifyProtoRepresenting returns a handler that verifies that the body of the request is a valid protobuf
-//representation of the passed message.
-//
-//VerifyProtoRepresenting also verifies that the request's content type is application/x-protobuf
-func (g GHTTPWithGomega) VerifyProtoRepresenting(expected proto.Message) http.HandlerFunc {
-	return CombineHandlers(
-		g.VerifyContentType("application/x-protobuf"),
-		func(w http.ResponseWriter, req *http.Request) {
-			body, err := gutil.ReadAll(req.Body)
-			g.gomega.Expect(err).ShouldNot(HaveOccurred())
-			req.Body.Close()
-
-			expectedType := reflect.TypeOf(expected)
-			actualValuePtr := reflect.New(expectedType.Elem())
-
-			actual, ok := actualValuePtr.Interface().(proto.Message)
-			g.gomega.Expect(ok).Should(BeTrue(), "Message value is not a proto.Message")
-
-			err = proto.Unmarshal(body, actual)
-			g.gomega.Expect(err).ShouldNot(HaveOccurred(), "Failed to unmarshal protobuf")
-
-			g.gomega.Expect(actual).Should(Equal(expected), "ProtoBuf Mismatch")
-		},
-	)
-}
-
-func copyHeader(src http.Header, dst http.Header) {
-	for key, value := range src {
-		dst[key] = value
-	}
-}
-
-/*
-RespondWith returns a handler that responds to a request with the specified status code and body
-
-Body may be a string or []byte
-
-Also, RespondWith can be given an optional http.Header.  The headers defined therein will be added to the response headers.
-*/
-func (g GHTTPWithGomega) RespondWith(statusCode int, body interface{}, optionalHeader ...http.Header) http.HandlerFunc {
-	return func(w http.ResponseWriter, req *http.Request) {
-		if len(optionalHeader) == 1 {
-			copyHeader(optionalHeader[0], w.Header())
-		}
-		w.WriteHeader(statusCode)
-		switch x := body.(type) {
-		case string:
-			w.Write([]byte(x))
-		case []byte:
-			w.Write(x)
-		default:
-			g.gomega.Expect(body).Should(BeNil(), "Invalid type for body.  Should be string or []byte.")
-		}
-	}
-}
-
-/*
-RespondWithPtr returns a handler that responds to a request with the specified status code and body
-
-Unlike RespondWith, you pass RepondWithPtr a pointer to the status code and body allowing different tests
-to share the same setup but specify different status codes and bodies.
-
-Also, RespondWithPtr can be given an optional http.Header.  The headers defined therein will be added to the response headers.
-Since the http.Header can be mutated after the fact you don't need to pass in a pointer.
-*/
-func (g GHTTPWithGomega) RespondWithPtr(statusCode *int, body interface{}, optionalHeader ...http.Header) http.HandlerFunc {
-	return func(w http.ResponseWriter, req *http.Request) {
-		if len(optionalHeader) == 1 {
-			copyHeader(optionalHeader[0], w.Header())
-		}
-		w.WriteHeader(*statusCode)
-		if body != nil {
-			switch x := (body).(type) {
-			case *string:
-				w.Write([]byte(*x))
-			case *[]byte:
-				w.Write(*x)
-			default:
-				g.gomega.Expect(body).Should(BeNil(), "Invalid type for body.  Should be string or []byte.")
-			}
-		}
-	}
-}
-
-/*
-RespondWithJSONEncoded returns a handler that responds to a request with the specified status code and a body
-containing the JSON-encoding of the passed in object
-
-Also, RespondWithJSONEncoded can be given an optional http.Header.  The headers defined therein will be added to the response headers.
-*/
-func (g GHTTPWithGomega) RespondWithJSONEncoded(statusCode int, object interface{}, optionalHeader ...http.Header) http.HandlerFunc {
-	data, err := json.Marshal(object)
-	g.gomega.Expect(err).ShouldNot(HaveOccurred())
-
-	var headers http.Header
-	if len(optionalHeader) == 1 {
-		headers = optionalHeader[0]
-	} else {
-		headers = make(http.Header)
-	}
-	if _, found := headers["Content-Type"]; !found {
-		headers["Content-Type"] = []string{"application/json"}
-	}
-	return RespondWith(statusCode, string(data), headers)
-}
-
-/*
-RespondWithJSONEncodedPtr behaves like RespondWithJSONEncoded but takes a pointer
-to a status code and object.
-
-This allows different tests to share the same setup but specify different status codes and JSON-encoded
-objects.
-
-Also, RespondWithJSONEncodedPtr can be given an optional http.Header.  The headers defined therein will be added to the response headers.
-Since the http.Header can be mutated after the fact you don't need to pass in a pointer.
-*/
-func (g GHTTPWithGomega) RespondWithJSONEncodedPtr(statusCode *int, object interface{}, optionalHeader ...http.Header) http.HandlerFunc {
-	return func(w http.ResponseWriter, req *http.Request) {
-		data, err := json.Marshal(object)
-		g.gomega.Expect(err).ShouldNot(HaveOccurred())
-		var headers http.Header
-		if len(optionalHeader) == 1 {
-			headers = optionalHeader[0]
-		} else {
-			headers = make(http.Header)
-		}
-		if _, found := headers["Content-Type"]; !found {
-			headers["Content-Type"] = []string{"application/json"}
-		}
-		copyHeader(headers, w.Header())
-		w.WriteHeader(*statusCode)
-		w.Write(data)
-	}
-}
-
-//RespondWithProto returns a handler that responds to a request with the specified status code and a body
-//containing the protobuf serialization of the provided message.
-//
-//Also, RespondWithProto can be given an optional http.Header.  The headers defined therein will be added to the response headers.
-func (g GHTTPWithGomega) RespondWithProto(statusCode int, message proto.Message, optionalHeader ...http.Header) http.HandlerFunc {
-	return func(w http.ResponseWriter, req *http.Request) {
-		data, err := proto.Marshal(message)
-		g.gomega.Expect(err).ShouldNot(HaveOccurred())
-
-		var headers http.Header
-		if len(optionalHeader) == 1 {
-			headers = optionalHeader[0]
-		} else {
-			headers = make(http.Header)
-		}
-		if _, found := headers["Content-Type"]; !found {
-			headers["Content-Type"] = []string{"application/x-protobuf"}
-		}
-		copyHeader(headers, w.Header())
-
-		w.WriteHeader(statusCode)
-		w.Write(data)
-	}
-}
-
-func VerifyRequest(method string, path interface{}, rawQuery ...string) http.HandlerFunc {
-	return NewGHTTPWithGomega(gomega.Default).VerifyRequest(method, path, rawQuery...)
-}
-
-func VerifyContentType(contentType string) http.HandlerFunc {
-	return NewGHTTPWithGomega(gomega.Default).VerifyContentType(contentType)
-}
-
-func VerifyMimeType(mimeType string) http.HandlerFunc {
-	return NewGHTTPWithGomega(gomega.Default).VerifyMimeType(mimeType)
-}
-
-func VerifyBasicAuth(username string, password string) http.HandlerFunc {
-	return NewGHTTPWithGomega(gomega.Default).VerifyBasicAuth(username, password)
-}
-
-func VerifyHeader(header http.Header) http.HandlerFunc {
-	return NewGHTTPWithGomega(gomega.Default).VerifyHeader(header)
-}
-
-func VerifyHeaderKV(key string, values ...string) http.HandlerFunc {
-	return NewGHTTPWithGomega(gomega.Default).VerifyHeaderKV(key, values...)
-}
-
-func VerifyBody(expectedBody []byte) http.HandlerFunc {
-	return NewGHTTPWithGomega(gomega.Default).VerifyBody(expectedBody)
-}
-
-func VerifyJSON(expectedJSON string) http.HandlerFunc {
-	return NewGHTTPWithGomega(gomega.Default).VerifyJSON(expectedJSON)
-}
-
-func VerifyJSONRepresenting(object interface{}) http.HandlerFunc {
-	return NewGHTTPWithGomega(gomega.Default).VerifyJSONRepresenting(object)
-}
-
-func VerifyForm(values url.Values) http.HandlerFunc {
-	return NewGHTTPWithGomega(gomega.Default).VerifyForm(values)
-}
-
-func VerifyFormKV(key string, values ...string) http.HandlerFunc {
-	return NewGHTTPWithGomega(gomega.Default).VerifyFormKV(key, values...)
-}
-
-func VerifyProtoRepresenting(expected proto.Message) http.HandlerFunc {
-	return NewGHTTPWithGomega(gomega.Default).VerifyProtoRepresenting(expected)
-}
-
-func RespondWith(statusCode int, body interface{}, optionalHeader ...http.Header) http.HandlerFunc {
-	return NewGHTTPWithGomega(gomega.Default).RespondWith(statusCode, body, optionalHeader...)
-}
-
-func RespondWithPtr(statusCode *int, body interface{}, optionalHeader ...http.Header) http.HandlerFunc {
-	return NewGHTTPWithGomega(gomega.Default).RespondWithPtr(statusCode, body, optionalHeader...)
-}
-
-func RespondWithJSONEncoded(statusCode int, object interface{}, optionalHeader ...http.Header) http.HandlerFunc {
-	return NewGHTTPWithGomega(gomega.Default).RespondWithJSONEncoded(statusCode, object, optionalHeader...)
-}
-
-func RespondWithJSONEncodedPtr(statusCode *int, object interface{}, optionalHeader ...http.Header) http.HandlerFunc {
-	return NewGHTTPWithGomega(gomega.Default).RespondWithJSONEncodedPtr(statusCode, object, optionalHeader...)
-}
-
-func RespondWithProto(statusCode int, message proto.Message, optionalHeader ...http.Header) http.HandlerFunc {
-	return NewGHTTPWithGomega(gomega.Default).RespondWithProto(statusCode, message, optionalHeader...)
-}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 166b999d6..ee30f6ca6 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -31,10 +31,6 @@ github.com/containernetworking/cni/pkg/types/create
 github.com/containernetworking/cni/pkg/types/internal
 github.com/containernetworking/cni/pkg/utils
 github.com/containernetworking/cni/pkg/version
-# github.com/containernetworking/plugins v1.2.0
-## explicit; go 1.17
-github.com/containernetworking/plugins/pkg/ns
-github.com/containernetworking/plugins/pkg/testutils
 # github.com/cpuguy83/go-md2man/v2 v2.0.2
 ## explicit; go 1.11
 github.com/cpuguy83/go-md2man/v2/md2man
@@ -295,7 +291,6 @@ github.com/onsi/gomega
 github.com/onsi/gomega/format
 github.com/onsi/gomega/gbytes
 github.com/onsi/gomega/gexec
-github.com/onsi/gomega/ghttp
 github.com/onsi/gomega/internal
 github.com/onsi/gomega/internal/gutil
 github.com/onsi/gomega/matchers


Test patch:
diff --git a/cmd/spiderpool/cmd/cmd_suite_test.go b/cmd/spiderpool/cmd/cmd_suite_test.go
deleted file mode 100644
index e2804d3a8..000000000
--- a/cmd/spiderpool/cmd/cmd_suite_test.go
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2022 Authors of spidernet-io
-// SPDX-License-Identifier: Apache-2.0
-
-package cmd_test
-
-import (
-	"testing"
-
-	. "github.com/onsi/ginkgo/v2"
-	. "github.com/onsi/gomega"
-)
-
-func TestCmd(t *testing.T) {
-	RegisterFailHandler(Fail)
-	RunSpecs(t, "Cmd Suite")
-}
diff --git a/cmd/spiderpool/cmd/command_test.go b/cmd/spiderpool/cmd/command_test.go
deleted file mode 100644
index a257b2795..000000000
--- a/cmd/spiderpool/cmd/command_test.go
+++ /dev/null
@@ -1,575 +0,0 @@
-// Copyright 2022 Authors of spidernet-io
-// SPDX-License-Identifier: Apache-2.0
-
-package cmd_test
-
-import (
-	"encoding/json"
-	"fmt"
-	"net"
-	"net/http"
-	"os"
-	"reflect"
-	"time"
-
-	"github.com/agiledragon/gomonkey/v2"
-	"github.com/containernetworking/cni/pkg/skel"
-	"github.com/containernetworking/cni/pkg/types"
-	current "github.com/containernetworking/cni/pkg/types/100"
-	"github.com/containernetworking/plugins/pkg/testutils"
-	. "github.com/onsi/ginkgo/v2"
-	. "github.com/onsi/gomega"
-	"github.com/onsi/gomega/ghttp"
-	"k8s.io/utils/pointer"
-
-	"github.com/spidernet-io/spiderpool/api/v1/agent/models"
-	"github.com/spidernet-io/spiderpool/api/v1/agent/server/restapi/connectivity"
-	"github.com/spidernet-io/spiderpool/api/v1/agent/server/restapi/daemonset"
-	agentcmd "github.com/spidernet-io/spiderpool/cmd/spiderpool-agent/cmd"
-	"github.com/spidernet-io/spiderpool/cmd/spiderpool/cmd"
-	"github.com/spidernet-io/spiderpool/pkg/constant"
-	"github.com/spidernet-io/spiderpool/pkg/logutils"
-)
-
-const ifName string = "eth0"
-const nsPath string = "/some/where"
-const containerID string = "dummy"
-const CNITimeoutSec = 120
-const CNILogFilePath = "/tmp/spiderpool.log"
-
-const (
-	healthCheckRoute = "/v1/ipam/healthy"
-	ipamReqRoute     = "/v1/ipam/ip"
-)
-
-const CNIVersion010 = "0.1.0"
-const CNIVersion020 = "0.2.0"
-
-var cniVersion string
-var args *skel.CmdArgs
-var netConf cmd.NetConf
-var sockPath string
-
-var addChan, delChan chan struct{}
-
-type ConfigWorkableSets struct {
-	// decide the IPAM plugin configuration is all good
-	isPreConfigGood bool
-	// decide the spiderpool agent is able to respond route 'healthy'
-	isHealthy bool
-	// decide the spiderpool agent is able to assign IP
-	isPostIPAM bool
-	// decide the spiderpool agent is able to release IP
-	isDeleteIPAM bool
-}
-
-var _ = Describe("spiderpool plugin", Label("unitest", "ipam_plugin_test"), func() {
-	BeforeEach(func() {
-		// generate one temp unix file.
-		tempDir := GinkgoT().TempDir()
-		sockPath = tempDir + "/tmp.sock"
-
-		// cleanup the temp unix file at the end.
-		DeferCleanup(func() {
-			err := os.RemoveAll(sockPath)
-			Expect(err).NotTo(HaveOccurred())
-			err = os.RemoveAll(CNILogFilePath)
-			Expect(err).NotTo(HaveOccurred())
-		})
-
-		args = &skel.CmdArgs{
-			ContainerID: containerID,
-			Netns:       nsPath,
-			IfName:      ifName,
-		}
-
-		cniVersion = cmd.CniVersion031
-
-		netConf = cmd.NetConf{
-			CNIVersion: cniVersion,
-			IPAM: cmd.IPAMConfig{
-				LogLevel:           logutils.LogDebugLevelStr,
-				LogFilePath:        CNILogFilePath,
-				IPAMUnixSocketPath: sockPath,
-			},
-		}
-
-		addChan = make(chan struct{})
-		delChan = make(chan struct{})
-	})
-
-	Context("mock ipam plugin interacts with agent through unix socket", func() {
-		var server *ghttp.Server
-		BeforeEach(func() {
-			listener, err := net.Listen("unix", sockPath)
-			Expect(err).NotTo(HaveOccurred())
-			server = ghttp.NewUnstartedServer()
-			server.HTTPTestServer.Listener = listener
-			server.Start()
-
-			DeferCleanup(func() {
-				server.Close()
-			})
-		})
-
-		DescribeTable("test cmdAdd",
-			func(configSets ConfigWorkableSets, cmdArgs func() *skel.CmdArgs, mockServerResponse func() *models.IpamAddResponse, expectResponse func() *current.Result) {
-				var ipamPostHandleFunc http.HandlerFunc
-
-				// GET /v1/ipam/healthy
-				server.RouteToHandler(http.MethodGet, healthCheckRoute, ghttp.CombineHandlers(getHealthHandleFunc(configSets.isHealthy)))
-
-				// POST /v1/ipam/ip
-				if configSets.isPostIPAM {
-					// You must pre-define this even if the mockServerResponse is nil!
-					// And mockServerResponse is nil only use for bad health check!
-					var mockServerResp *models.IpamAddResponse
-					if nil != mockServerResponse {
-						mockServerResp = mockServerResponse()
-					}
-					ipamPostHandleFunc = ghttp.RespondWithJSONEncoded(daemonset.PostIpamIpsOKCode, mockServerResp)
-				} else {
-					ipamPostHandleFunc = ghttp.RespondWithJSONEncoded(daemonset.DeleteIpamIPFailureCode, nil)
-				}
-				server.RouteToHandler(http.MethodPost, ipamReqRoute, ghttp.CombineHandlers(ipamPostHandleFunc))
-
-				// start client test.
-				r, _, err := testutils.CmdAddWithArgs(cmdArgs(), func() error {
-					return cmd.CmdAdd(cmdArgs())
-				})
-
-				// bad response check
-				var expectErr error
-				if !configSets.isPreConfigGood {
-					Expect(err).To(HaveOccurred())
-					By("Expect to match error: " + err.Error())
-					return
-				} else if !configSets.isHealthy {
-					expectErr = cmd.ErrAgentHealthCheck
-				} else if !configSets.isPostIPAM {
-					expectErr = cmd.ErrPostIPAM
-				} else {
-					Expect(err).NotTo(HaveOccurred())
-				}
-
-				if expectErr != nil {
-					Expect(err).To(HaveOccurred())
-					Expect(err).Should(MatchError(expectErr))
-					return
-				}
-
-				addResult, err := current.GetResult(r)
-				Expect(err).NotTo(HaveOccurred())
-
-				var expectResp *current.Result
-				if nil != expectResponse {
-					expectResp = expectResponse()
-				} else {
-					Fail("You must define expectResp if every route good in CmdAdd situation.")
-				}
-
-				// No need to check result.CNIVersion since cni types 100 library would hard code it with "1.0.0"
-
-				// check Result.DNS
-				Expect(reflect.DeepEqual(addResult.DNS, expectResp.DNS)).To(Equal(true))
-
-				// check Result.IPs
-				Expect(reflect.DeepEqual(addResult.IPs, expectResp.IPs)).To(Equal(true))
-
-				// check Result.Routes
-				Expect(reflect.DeepEqual(addResult.Routes, expectResp.Routes))
-			},
-			Entry("returning an error on bad health check with ADD", ConfigWorkableSets{isPreConfigGood: true, isHealthy: false, isPostIPAM: true}, func() *skel.CmdArgs {
-				netConfBytes, err := json.Marshal(netConf)
-				Expect(err).NotTo(HaveOccurred())
-				args.StdinData = netConfBytes
-				return args
-			}, nil, nil),
-			Entry("returning an error on POST IPAM with ADD", ConfigWorkableSets{isPreConfigGood: true, isHealthy: true, isPostIPAM: false}, func() *skel.CmdArgs {
-				netConfBytes, err := json.Marshal(netConf)
-				Expect(err).NotTo(HaveOccurred())
-				args.StdinData = netConfBytes
-				return args
-			}, nil, nil),
-			Entry(fmt.Sprintf("allocates addresses with ADD in CNI version '%s'", cmd.CniVersion031), ConfigWorkableSets{isPreConfigGood: true, isHealthy: true, isPostIPAM: true}, func() *skel.CmdArgs {
-				netConfBytes, err := json.Marshal(netConf)
-				Expect(err).NotTo(HaveOccurred())
-				args.StdinData = netConfBytes
-				return args
-			}, func() *models.IpamAddResponse {
-				ipamAddResp := &models.IpamAddResponse{
-					DNS: &models.DNS{
-						Domain:      "local",
-						Nameservers: []string{"1.2.3.1"},
-						Options:     []string{"somedomain.com"},
-						Search:      []string{"foo"},
-					},
-					Ips: []*models.IPConfig{
-						{
-							Address: pointer.String("1.2.3.30/24"),
-							Gateway: "1.2.3.1",
-							Nic:     pointer.String("eth0"),
-							Version: pointer.Int64(constant.IPv4),
-						},
-						{
-							Address: pointer.String("fc00:f853:ccd:e793:f::fc/64"),
-							Gateway: "fc00:f853:ccd:e793:f::2",
-							Nic:     pointer.String("eth0"),
-							Version: pointer.Int64(constant.IPv6),
-						},
-					},
-					Routes: []*models.Route{{IfName: pointer.String("eth0"), Dst: pointer.String("15.5.6.0/24"), Gw: pointer.String("1.2.3.2")}},
-				}
-
-				return ipamAddResp
-			}, func() *current.Result {
-				expectResult := new(current.Result)
-				// CNIVersion
-				expectResult.CNIVersion = cniVersion
-				// DNS
-				expectResult.DNS = types.DNS{
-					Nameservers: []string{"1.2.3.1"},
-					Domain:      "local",
-					Search:      []string{"foo"},
-					Options:     []string{"somedomain.com"},
-				}
-				// IPs
-				expectResult.IPs = []*current.IPConfig{
-					{Gateway: net.ParseIP("1.2.3.1"), Address: net.IPNet{IP: net.ParseIP("1.2.3.30"), Mask: net.CIDRMask(24, 32)}},
-					{Gateway: net.ParseIP("fc00:f853:ccd:e793:f::2"), Address: net.IPNet{IP: net.ParseIP("fc00:f853:ccd:e793:f::fc"), Mask: net.CIDRMask(64, 128)}},
-				}
-
-				// Routes
-				_, ipNet, _ := net.ParseCIDR("15.5.6.0/24")
-				expectResult.Routes = []*types.Route{{Dst: *ipNet, GW: net.ParseIP("1.2.3.2")}}
-				//Interfaces
-				expectResult.Interfaces = []*current.Interface{{Name: ifName}}
-				return expectResult
-			}),
-			Entry(fmt.Sprintf("support CNI version '%s'", cmd.CniVersion030), ConfigWorkableSets{isPreConfigGood: true, isHealthy: true, isPostIPAM: true}, func() *skel.CmdArgs {
-				netConf.CNIVersion = cmd.CniVersion030
-				netConfBytes, err := json.Marshal(netConf)
-				Expect(err).NotTo(HaveOccurred())
-				args.StdinData = netConfBytes
-				return args
-			}, func() *models.IpamAddResponse {
-				ipamAddResp := &models.IpamAddResponse{
-					DNS: &models.DNS{
-						Domain:      "local",
-						Nameservers: []string{"10.1.0.2"},
-						Options:     []string{"domain.com"},
-						Search:      []string{"bar"},
-					},
-					Ips: []*models.IPConfig{
-						{
-							Address: pointer.String("10.1.0.6/24"),
-							Gateway: "10.1.0.2",
-							Nic:     pointer.String(ifName),
-							Version: pointer.Int64(constant.IPv4),
-						},
-					},
-				}
-
-				return ipamAddResp
-			}, func() *current.Result {
-				expectResult := new(current.Result)
-				// CNIVersion
-				expectResult.CNIVersion = cmd.CniVersion030
-				// DNS
-				expectResult.DNS = types.DNS{
-					Nameservers: []string{"10.1.0.2"},
-					Domain:      "local",
-					Search:      []string{"bar"},
-					Options:     []string{"domain.com"},
-				}
-				// IPs
-				expectResult.IPs = []*current.IPConfig{
-					{Gateway: net.ParseIP("10.1.0.2"), Address: net.IPNet{IP: net.ParseIP("10.1.0.6"), Mask: net.CIDRMask(24, 32)}},
-				}
-				//Interfaces
-				expectResult.Interfaces = []*current.Interface{{Name: ifName}}
-				return expectResult
-			}),
-			Entry(fmt.Sprintf("support CNI version '%s'", cmd.CniVersion040), ConfigWorkableSets{isPreConfigGood: true, isHealthy: true, isPostIPAM: true}, func() *skel.CmdArgs {
-				netConf.CNIVersion = cmd.CniVersion030
-				netConfBytes, err := json.Marshal(netConf)
-				Expect(err).NotTo(HaveOccurred())
-				args.StdinData = netConfBytes
-				return args
-			}, func() *models.IpamAddResponse {
-				ipamAddResp := &models.IpamAddResponse{
-					DNS: &models.DNS{},
-					Ips: []*models.IPConfig{
-						{
-							Address: pointer.String("10.1.0.7/24"),
-							Nic:     pointer.String(ifName),
-							Version: pointer.Int64(constant.IPv4),
-						},
-					},
-				}
-
-				return ipamAddResp
-			}, func() *current.Result {
-				expectResult := new(current.Result)
-				// CNIVersion
-				expectResult.CNIVersion = cmd.CniVersion040
-				// DNS
-				expectResult.DNS = types.DNS{}
-				// IPs
-				expectResult.IPs = []*current.IPConfig{
-					{Address: net.IPNet{IP: net.ParseIP("10.1.0.7"), Mask: net.CIDRMask(24, 32)}},
-				}
-				//Interfaces
-				expectResult.Interfaces = []*current.Interface{{Name: ifName}}
-				return expectResult
-			}),
-			Entry(fmt.Sprintf("support CNI version '%s'", CNIVersion010), ConfigWorkableSets{isPreConfigGood: false, isHealthy: true, isPostIPAM: true}, func() *skel.CmdArgs {
-				netConf.CNIVersion = CNIVersion010
-				netConfBytes, err := json.Marshal(netConf)
-				Expect(err).NotTo(HaveOccurred())
-				args.StdinData = netConfBytes
-				return args
-			}, nil, nil),
-			Entry(fmt.Sprintf("support CNI version '%s'", CNIVersion020), ConfigWorkableSets{isPreConfigGood: false, isHealthy: true, isPostIPAM: true}, func() *skel.CmdArgs {
-				netConf.CNIVersion = CNIVersion010
-				netConfBytes, err := json.Marshal(netConf)
-				Expect(err).NotTo(HaveOccurred())
-				args.StdinData = netConfBytes
-				return args
-			}, nil, nil),
-		)
-
-		DescribeTable("test cmdDel",
-			func(configSets ConfigWorkableSets, cmdArgs func() *skel.CmdArgs) {
-				var ipamDeleteHandleFunc http.HandlerFunc
-
-				// GET /v1/ipam/healthy
-				server.RouteToHandler(http.MethodGet, healthCheckRoute, ghttp.CombineHandlers(getHealthHandleFunc(configSets.isHealthy)))
-
-				// DELETE /v1/ipam/ip
-				if configSets.isDeleteIPAM {
-					ipamDeleteHandleFunc = ghttp.RespondWith(daemonset.DeleteIpamIPOKCode, nil)
-				} else {
-					ipamDeleteHandleFunc = ghttp.RespondWith(daemonset.DeleteIpamIPFailureCode, nil)
-				}
-				server.RouteToHandler(http.MethodDelete, ipamReqRoute, ghttp.CombineHandlers(ipamDeleteHandleFunc))
-
-				// start client test
-				err := testutils.CmdDelWithArgs(cmdArgs(), func() error {
-					return cmd.CmdDel(cmdArgs())
-				})
-
-				// bad response check
-				var expectErr error
-				if !configSets.isPreConfigGood {
-					Expect(err).To(HaveOccurred())
-					By("Expect to match error: " + err.Error())
-					return
-				} else if !configSets.isHealthy {
-					expectErr = cmd.ErrAgentHealthCheck
-				} else if !configSets.isDeleteIPAM {
-					// the CNI binary cmdDel won't return err
-					expectErr = nil
-				} else {
-					Expect(err).NotTo(HaveOccurred())
-				}
-
-				if expectErr != nil {
-					Expect(err).To(HaveOccurred())
-					Expect(err).Should(MatchError(expectErr))
-					return
-				}
-			},
-			Entry("returning an error on bad health check with DEL", ConfigWorkableSets{isPreConfigGood: true, isHealthy: false, isDeleteIPAM: true}, func() *skel.CmdArgs {
-				netConf.IPAM.LogLevel = logutils.LogInfoLevelStr
-				netConfBytes, err := json.Marshal(netConf)
-				Expect(err).NotTo(HaveOccurred())
-				args.StdinData = netConfBytes
-				return args
-			}),
-			Entry("release addresses with DEL successfully", ConfigWorkableSets{isPreConfigGood: true, isHealthy: true, isDeleteIPAM: true}, func() *skel.CmdArgs {
-				netConf.IPAM.LogLevel = logutils.LogWarnLevelStr
-				netConfBytes, err := json.Marshal(netConf)
-				Expect(err).NotTo(HaveOccurred())
-				args.StdinData = netConfBytes
-				return args
-			}),
-			Entry("failed to release addresses with bad spiderpool agent response", ConfigWorkableSets{isPreConfigGood: true, isHealthy: true, isDeleteIPAM: false}, func() *skel.CmdArgs {
-				netConf.IPAM.LogLevel = logutils.LogErrorLevelStr
-				netConfBytes, err := json.Marshal(netConf)
-				Expect(err).NotTo(HaveOccurred())
-				args.StdinData = netConfBytes
-				return args
-			}),
-		)
-	})
-
-	Describe("test ipam plugin configuration ", func() {
-		It(fmt.Sprintf("[%s] is returning an error on conf broken with ADD/DEL", cniVersion), func() {
-			confBytes, err := json.Marshal(netConf)
-			Expect(err).NotTo(HaveOccurred())
-			confBytes = append(confBytes, []byte("}")...)
-			args.StdinData = confBytes
-
-			// Allocate the IP
-			go func() {
-				defer GinkgoRecover()
-
-				_, _, err := testutils.CmdAddWithArgs(args, func() error {
-					return cmd.CmdAdd(args)
-				})
-				Expect(err).To(HaveOccurred())
-				close(addChan)
-			}()
-			Eventually(addChan).WithTimeout(CNITimeoutSec * time.Second).Should(BeClosed())
-
-			// Release the IP
-			go func() {
-				defer GinkgoRecover()
-
-				err = testutils.CmdDelWithArgs(args, func() error {
-					return cmd.CmdDel(args)
-				})
-				Expect(err).To(HaveOccurred())
-				close(delChan)
-			}()
-			Eventually(delChan).WithTimeout(CNITimeoutSec * time.Second).Should(BeClosed())
-		})
-
-		It(fmt.Sprintf("[%s] is returning an error on bad log configuration with ADD/DEL", cniVersion), func() {
-			netConf.IPAM.LogLevel = "bad"
-			netConfBytes, err := json.Marshal(netConf)
-			Expect(err).NotTo(HaveOccurred())
-			args.StdinData = netConfBytes
-
-			// Allocate the IP
-			go func() {
-				defer GinkgoRecover()
-
-				_, _, err := testutils.CmdAddWithArgs(args, func() error {
-					return cmd.CmdAdd(args)
-				})
-				Expect(err).To(HaveOccurred())
-				close(addChan)
-			}()
-			Eventually(addChan).WithTimeout(CNITimeoutSec * time.Second).Should(BeClosed())
-
-			// Release the IP
-			go func() {
-				defer GinkgoRecover()
-
-				err = testutils.CmdDelWithArgs(args, func() error {
-					return cmd.CmdDel(args)
-				})
-				Expect(err).To(HaveOccurred())
-				close(delChan)
-			}()
-			Eventually(delChan).WithTimeout(CNITimeoutSec * time.Second).Should(BeClosed())
-		})
-
-		It("Check default network configuration", func() {
-			// set some configurations with empty value.
-			netConf.IPAM.LogLevel = ""
-			netConf.IPAM.IPAMUnixSocketPath = ""
-
-			netConfBytes, err := json.Marshal(netConf)
-			Expect(err).NotTo(HaveOccurred())
-
-			conf, err := cmd.LoadNetConf(netConfBytes)
-			Expect(err).NotTo(HaveOccurred())
-
-			Expect(conf.IPAM.LogLevel).Should(Equal(cmd.DefaultLogLevelStr))
-			Expect(conf.IPAM.IPAMUnixSocketPath).Should(Equal(constant.DefaultIPAMUnixSocketPath))
-		})
-
-		It("Failed to load args with cmdAdd and cmdDel", func() {
-			patches := gomonkey.ApplyFuncSeq(types.LoadArgs, []gomonkey.OutputCell{
-				{Values: gomonkey.Params{constant.ErrUnknown}},
-				{Values: gomonkey.Params{constant.ErrUnknown}},
-			})
-
-			defer patches.Reset()
-
-			confBytes, err := json.Marshal(netConf)
-			Expect(err).NotTo(HaveOccurred())
-			args.StdinData = confBytes
-
-			// Allocate the IP
-			go func() {
-				defer GinkgoRecover()
-
-				_, _, err := testutils.CmdAddWithArgs(args, func() error {
-					return cmd.CmdAdd(args)
-				})
-				Expect(err).To(HaveOccurred())
-				Expect(err).To(MatchError(constant.ErrUnknown))
-				close(addChan)
-			}()
-			Eventually(addChan).WithTimeout(CNITimeoutSec * time.Second).Should(BeClosed())
-
-			// Release the IP
-			go func() {
-				defer GinkgoRecover()
-
-				err = testutils.CmdDelWithArgs(args, func() error {
-					return cmd.CmdDel(args)
-				})
-				Expect(err).To(HaveOccurred())
-				Expect(err).To(MatchError(constant.ErrUnknown))
-				close(delChan)
-			}()
-			Eventually(delChan).WithTimeout(CNITimeoutSec * time.Second).Should(BeClosed())
-		})
-
-		It("Failed to new agent openAPI unix client with cmdAdd and cmdDel", func() {
-			patches := gomonkey.ApplyFuncSeq(agentcmd.NewAgentOpenAPIUnixClient, []gomonkey.OutputCell{
-				{Values: gomonkey.Params{nil, constant.ErrUnknown}},
-				{Values: gomonkey.Params{nil, constant.ErrUnknown}},
-			})
-			defer patches.Reset()
-
-			confBytes, err := json.Marshal(netConf)
-			Expect(err).NotTo(HaveOccurred())
-			args.StdinData = confBytes
-
-			// Allocate the IP
-			go func() {
-				defer GinkgoRecover()
-
-				_, _, err := testutils.CmdAddWithArgs(args, func() error {
-					return cmd.CmdAdd(args)
-				})
-				Expect(err).To(HaveOccurred())
-				Expect(err).To(MatchError(constant.ErrUnknown))
-				close(addChan)
-			}()
-			Eventually(addChan).WithTimeout(CNITimeoutSec * time.Second).Should(BeClosed())
-
-			// Release the IP
-			go func() {
-				defer GinkgoRecover()
-
-				err = testutils.CmdDelWithArgs(args, func() error {
-					return cmd.CmdDel(args)
-				})
-				Expect(err).To(HaveOccurred())
-				Expect(err).To(MatchError(constant.ErrUnknown))
-				close(delChan)
-			}()
-			Eventually(delChan).WithTimeout(CNITimeoutSec * time.Second).Should(BeClosed())
-		})
-	})
-
-})
-
-func getHealthHandleFunc(isHealthy bool) http.HandlerFunc {
-	var healthHandleFunc http.HandlerFunc
-
-	if isHealthy {
-		healthHandleFunc = ghttp.RespondWith(connectivity.GetIpamHealthyOKCode, nil)
-	} else {
-		healthHandleFunc = ghttp.RespondWith(connectivity.GetIpamHealthyInternalServerErrorCode, nil)
-	}
-
-	return healthHandleFunc
-}
diff --git a/pkg/applicationcontroller/applicationinformers/utils_test.go b/pkg/applicationcontroller/applicationinformers/utils_test.go
index ed715ad25..526ab6b5a 100644
--- a/pkg/applicationcontroller/applicationinformers/utils_test.go
+++ b/pkg/applicationcontroller/applicationinformers/utils_test.go
@@ -11,8 +11,6 @@ import (
 	"github.com/agiledragon/gomonkey/v2"
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
-	kruisev1 "github.com/openkruise/kruise-api/apps/v1alpha1"
-	appsv1 "k8s.io/api/apps/v1"
 	corev1 "k8s.io/api/core/v1"
 	apitypes "k8s.io/apimachinery/pkg/types"
 	"k8s.io/utils/pointer"
@@ -26,88 +24,48 @@ import (
 )
 
 var _ = Describe("Utils", func() {
-	Context("test AutoPoolName", func() {
+	It("SubnetPoolName", Label("unitest", "SubnetPoolName"), func() {
 		controllerName := "test-name"
-		ipVersion := constant.IPv4
+		ipVersion := types.IPVersion(4)
 		ifName := "test-nic"
+		controllerUID := apitypes.UID("a-b-c-d")
+		lastOne := "d"
+		expectRes := fmt.Sprintf("auto-%s-v%d-%s-%s",
+			strings.ToLower(controllerName), ipVersion, ifName, strings.ToLower(lastOne))
 
-		It("normal controller name and uid", Label("unitest", "AutoPoolName"), func() {
-			controllerUID := apitypes.UID("a-b-c-d")
-			lastOne := "d"
-			expectRes := fmt.Sprintf("auto%d-%s-%s-%s",
-				ipVersion, strings.ToLower(controllerName), strings.ToLower(ifName), strings.ToLower(lastOne))
-
-			result := AutoPoolName(controllerName, ipVersion, ifName, controllerUID)
-			Expect(result).To(Equal(expectRes))
-		})
-
-		It("controller uid out of length", Label("unitest", "AutoPoolName"), func() {
-			controllerUID := apitypes.UID("a-b-c-defghi")
-			lastOne := "defgh"
-			expectRes := fmt.Sprintf("auto%d-%s-%s-%s",
-				ipVersion, strings.ToLower(controllerName), strings.ToLower(ifName), strings.ToLower(lastOne))
-
-			result := AutoPoolName(controllerName, ipVersion, ifName, controllerUID)
-			Expect(result).To(Equal(expectRes))
-		})
+		result := SubnetPoolName(controllerName, ipVersion, ifName, controllerUID)
+		Expect(result).To(Equal(expectRes))
 	})
 
-	Context("test corev1", func() {
+	It("ApplicationNamespacedName", Label("unitest", "AppLabelValue"), func() {
+		apiVersion := corev1.SchemeGroupVersion.String()
 		appKind := "test-kind"
 		appNS := "test-ns"
 		appName := "test-name"
+		expectResult := fmt.Sprintf("%s:%s:%s:%s", apiVersion, appKind, appNS, appName)
 
-		var appNamespacedName types.AppNamespacedName
-		BeforeEach(func() {
-			appNamespacedName.Kind = appKind
-			appNamespacedName.Namespace = appNS
-			appNamespacedName.Name = appName
-		})
-
-		It("no API Group", Label("unitest", "AppLabelValue"), func() {
-			// the corev1 only has API Version, its API Group is empty
-			apiVersion := corev1.SchemeGroupVersion.String()
-			expectResult := fmt.Sprintf("%s_%s_%s_%s", corev1.SchemeGroupVersion.Version, appKind, appNS, appName)
-
-			appNamespacedName.APIVersion = apiVersion
-			result := ApplicationNamespacedName(appNamespacedName)
-
-			Expect(result).To(Equal(expectResult))
-		})
-
-		It("test appsv1", Label("unitest", "AppLabelValue"), func() {
-			apiVersion := appsv1.SchemeGroupVersion.String()
-			expectResult := fmt.Sprintf("%s_%s_%s_%s_%s", appsv1.SchemeGroupVersion.Group, appsv1.SchemeGroupVersion.Version, appKind, appNS, appName)
+		appNamespacedName := types.AppNamespacedName{
+			APIVersion: apiVersion,
+			Kind:       appKind,
+			Namespace:  appNS,
+			Name:       appName,
+		}
+		result := ApplicationNamespacedName(appNamespacedName)
 
-			appNamespacedName.APIVersion = apiVersion
-			result := ApplicationNamespacedName(appNamespacedName)
-
-			Expect(result).To(Equal(expectResult))
-		})
+		Expect(result).To(Equal(expectResult))
 	})
 
 	Context("ParseApplicationNamespacedName", Label("unitest", "ParseApplicationNamespacedName"), func() {
-		appKind := "test-kind"
-		appNS := "test-ns"
-		appName := "test-name"
-
-		It("match appsv1 appNamespacedName", func() {
-			appNamespacedNameKey := fmt.Sprintf("%s_%s_%s_%s_%s", appsv1.SchemeGroupVersion.Group, appsv1.SchemeGroupVersion.Version, appKind, appNS, appName)
-
-			appNamespacedName, isMatch := ParseApplicationNamespacedName(appNamespacedNameKey)
-			Expect(isMatch).To(BeTrue())
-			Expect(appNamespacedName.APIVersion).Should(Equal(appsv1.SchemeGroupVersion.String()))
-			Expect(appNamespacedName.Kind).Should(Equal(appKind))
-			Expect(appNamespacedName.Namespace).Should(Equal(appNS))
-			Expect(appNamespacedName.Name).Should(Equal(appName))
-		})
-
-		It("match corev1 appNamespacedName", func() {
-			appNamespacedNameKey := fmt.Sprintf("%s_%s_%s_%s", corev1.SchemeGroupVersion.Version, appKind, appNS, appName)
+		It("match", func() {
+			apiVersion := corev1.SchemeGroupVersion.String()
+			appKind := "test-kind"
+			appNS := "test-ns"
+			appName := "test-name"
+			appNamespacedNameKey := fmt.Sprintf("%s:%s:%s:%s", apiVersion, appKind, appNS, appName)
 
 			appNamespacedName, isMatch := ParseApplicationNamespacedName(appNamespacedNameKey)
 			Expect(isMatch).To(BeTrue())
-			Expect(appNamespacedName.APIVersion).Should(Equal(corev1.SchemeGroupVersion.String()))
+			Expect(appNamespacedName.APIVersion).Should(Equal(apiVersion))
 			Expect(appNamespacedName.Kind).Should(Equal(appKind))
 			Expect(appNamespacedName.Namespace).Should(Equal(appNS))
 			Expect(appNamespacedName.Name).Should(Equal(appName))
@@ -502,74 +460,4 @@ var _ = Describe("Utils", func() {
 			Expect(err).To(HaveOccurred())
 		})
 	})
-
-	Context("GenerateGVR", Labels{"unitest", "GenerateGVR"}, func() {
-		It("appsv1-deployment", func() {
-			appNamespacedName := types.AppNamespacedName{
-				APIVersion: appsv1.SchemeGroupVersion.String(),
-				Kind:       constant.KindDeployment,
-				Namespace:  "test-ns",
-				Name:       "test-name",
-			}
-			gvr, err := GenerateGVR(appNamespacedName)
-			Expect(err).NotTo(HaveOccurred())
-			Expect(gvr.Group).To(Equal(appsv1.SchemeGroupVersion.Group))
-			Expect(gvr.Version).To(Equal(appsv1.SchemeGroupVersion.Version))
-			Expect(gvr.Resource).To(Equal("deployments"))
-		})
-
-		It("corev1-pod", func() {
-			appNamespacedName := types.AppNamespacedName{
-				APIVersion: corev1.SchemeGroupVersion.String(),
-				Kind:       constant.KindPod,
-				Namespace:  "test-ns",
-				Name:       "test-name",
-			}
-			gvr, err := GenerateGVR(appNamespacedName)
-			Expect(err).NotTo(HaveOccurred())
-			Expect(gvr.Group).To(BeEmpty())
-			Expect(gvr.Version).To(Equal(corev1.SchemeGroupVersion.Version))
-			Expect(gvr.Resource).To(Equal("pods"))
-		})
-
-		It("wrong APIVersion input", func() {
-			appNamespacedName := types.AppNamespacedName{
-				APIVersion: "/v1/",
-				Kind:       constant.KindPod,
-				Namespace:  "test-ns",
-				Name:       "test-name",
-			}
-			_, err := GenerateGVR(appNamespacedName)
-			Expect(err).To(HaveOccurred())
-		})
-	})
-
-	Context("IsThirdController", Label("unitest", "IsThirdController"), func() {
-		var appNamespacedName types.AppNamespacedName
-		BeforeEach(func() {
-			appNamespacedName.Namespace = "test-ns"
-			appNamespacedName.Name = "test-name"
-		})
-
-		It("appsv1-deployment", func() {
-			appNamespacedName.APIVersion = appsv1.SchemeGroupVersion.String()
-			appNamespacedName.Kind = constant.KindDeployment
-			isThirdController := IsThirdController(appNamespacedName)
-			Expect(isThirdController).To(BeFalse())
-		})
-
-		It("openkruise-clonset", func() {
-			appNamespacedName.APIVersion = kruisev1.SchemeGroupVersion.String()
-			appNamespacedName.Kind = "CloneSet"
-			isThirdController := IsThirdController(appNamespacedName)
-			Expect(isThirdController).To(BeTrue())
-		})
-
-		It("appsv1-ClonsSet", func() {
-			appNamespacedName.APIVersion = appsv1.SchemeGroupVersion.String()
-			appNamespacedName.Kind = "ClonsSet"
-			isThirdController := IsThirdController(appNamespacedName)
-			Expect(isThirdController).To(BeTrue())
-		})
-	})
 })
diff --git a/pkg/ippoolmanager/ippool_webhook_test.go b/pkg/ippoolmanager/ippool_webhook_test.go
index a50c7ebde..03b8fd282 100644
--- a/pkg/ippoolmanager/ippool_webhook_test.go
+++ b/pkg/ippoolmanager/ippool_webhook_test.go
@@ -1762,7 +1762,7 @@ var _ = Describe("IPPoolWebhook", Label("ippool_webhook_test"), func() {
 					subnetT.Spec.IPs = append(subnetT.Spec.IPs, "172.18.40.1-172.18.40.2")
 
 					autoPool := ipPoolT.DeepCopy()
-					autoPool.Labels = map[string]string{constant.LabelIPPoolOwnerApplication: applicationinformers.ApplicationNamespacedName(types.AppNamespacedName{
+					autoPool.Annotations = map[string]string{constant.AnnoSpiderSubnetPoolApp: applicationinformers.ApplicationNamespacedName(types.AppNamespacedName{
 						APIVersion: appsv1.SchemeGroupVersion.String(),
 						Kind:       constant.KindDeployment,
 						Namespace:  autoPool.Namespace,
@@ -1776,7 +1776,7 @@ var _ = Describe("IPPoolWebhook", Label("ippool_webhook_test"), func() {
 					Expect(err).NotTo(HaveOccurred())
 					poolIPPreAllocations := spiderpoolv2beta1.PoolIPPreAllocations{autoPool.Name: spiderpoolv2beta1.PoolIPPreAllocation{
 						IPs:         []string{"172.18.40.1"},
-						Application: pointer.String(autoPool.Labels[constant.LabelIPPoolOwnerApplication]),
+						Application: pointer.String(autoPool.Annotations[constant.AnnoSpiderSubnetPoolApp]),
 					}}
 					subnetAllocatedIPPools, err := convert.MarshalSubnetAllocatedIPPools(poolIPPreAllocations)
 					Expect(err).NotTo(HaveOccurred())
@@ -1799,7 +1799,7 @@ var _ = Describe("IPPoolWebhook", Label("ippool_webhook_test"), func() {
 					subnetT.Spec.IPs = append(subnetT.Spec.IPs, "172.18.40.1-172.18.40.2")
 
 					autoPool := ipPoolT.DeepCopy()
-					autoPool.Labels = map[string]string{constant.LabelIPPoolOwnerApplication: applicationinformers.ApplicationNamespacedName(types.AppNamespacedName{
+					autoPool.Annotations = map[string]string{constant.AnnoSpiderSubnetPoolApp: applicationinformers.ApplicationNamespacedName(types.AppNamespacedName{
 						APIVersion: appsv1.SchemeGroupVersion.String(),
 						Kind:       constant.KindDeployment,
 						Namespace:  autoPool.Namespace,
@@ -1813,7 +1813,7 @@ var _ = Describe("IPPoolWebhook", Label("ippool_webhook_test"), func() {
 					Expect(err).NotTo(HaveOccurred())
 					poolIPPreAllocations := spiderpoolv2beta1.PoolIPPreAllocations{autoPool.Name: spiderpoolv2beta1.PoolIPPreAllocation{
 						IPs:         []string{"172.18.40.1"},
-						Application: pointer.String(autoPool.Labels[constant.LabelIPPoolOwnerApplication]),
+						Application: pointer.String(autoPool.Annotations[constant.AnnoSpiderSubnetPoolApp]),
 					}}
 					subnetAllocatedIPPools, err := convert.MarshalSubnetAllocatedIPPools(poolIPPreAllocations)
 					Expect(err).NotTo(HaveOccurred())
@@ -1824,9 +1824,6 @@ var _ = Describe("IPPoolWebhook", Label("ippool_webhook_test"), func() {
 
 					newAutoPool := autoPool.DeepCopy()
 					anno := newAutoPool.GetAnnotations()
-					if anno == nil {
-						anno = make(map[string]string)
-					}
 					anno["aaa"] = "test"
 					newAutoPool.Annotations = anno
 
diff --git a/pkg/ippoolmanager/utils_test.go b/pkg/ippoolmanager/utils_test.go
deleted file mode 100644
index 4b63059fe..000000000
--- a/pkg/ippoolmanager/utils_test.go
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright 2022 Authors of spidernet-io
-// SPDX-License-Identifier: Apache-2.0
-
-package ippoolmanager
-
-import (
-	. "github.com/onsi/ginkgo/v2"
-	. "github.com/onsi/gomega"
-	appsv1 "k8s.io/api/apps/v1"
-	types2 "k8s.io/apimachinery/pkg/types"
-
-	"github.com/spidernet-io/spiderpool/pkg/applicationcontroller/applicationinformers"
-	"github.com/spidernet-io/spiderpool/pkg/constant"
-	spiderpoolv2beta1 "github.com/spidernet-io/spiderpool/pkg/k8s/apis/spiderpool.spidernet.io/v2beta1"
-	"github.com/spidernet-io/spiderpool/pkg/types"
-)
-
-var _ = Describe("IPPoolManager-utils", Label("ippool_manager_utils"), func() {
-	Context("IsAutoCreatedIPPool", Labels{"unitest", "IsAutoCreatedIPPool"}, func() {
-		It("normal IPPool", func() {
-			var pool spiderpoolv2beta1.SpiderIPPool
-
-			label := map[string]string{constant.LabelIPPoolOwnerApplication: applicationinformers.ApplicationNamespacedName(types.AppNamespacedName{
-				APIVersion: appsv1.SchemeGroupVersion.String(),
-				Kind:       constant.KindDeployment,
-				Namespace:  "test-ns",
-				Name:       "test-name",
-			})}
-			pool.SetLabels(label)
-
-			isAutoCreatedIPPool := IsAutoCreatedIPPool(&pool)
-			Expect(isAutoCreatedIPPool).To(BeTrue())
-		})
-
-		It("auto-created IPPool", func() {
-			var pool spiderpoolv2beta1.SpiderIPPool
-
-			isAutoCreatedIPPool := IsAutoCreatedIPPool(&pool)
-			Expect(isAutoCreatedIPPool).To(BeFalse())
-		})
-	})
-
-	Context("Test Auto IPPool PodAffinity", Labels{"unitest", "AutoPool-PodAffinity"}, func() {
-		It("match auto-created IPPool affinity", func() {
-			podTopController := types.PodTopController{
-				AppNamespacedName: types.AppNamespacedName{
-					APIVersion: appsv1.SchemeGroupVersion.String(),
-					Kind:       constant.KindDeployment,
-					Namespace:  "test-ns",
-					Name:       "test-name",
-				},
-				UID: types2.UID("a-b-c"),
-				APP: nil,
-			}
-
-			podAffinity := NewAutoPoolPodAffinity(podTopController)
-
-			isMatchAutoPoolAffinity := IsMatchAutoPoolAffinity(podAffinity, podTopController)
-			Expect(isMatchAutoPoolAffinity).To(BeTrue())
-		})
-
-		It("not match auto-created IPPool affinity", func() {
-			podTopController := types.PodTopController{
-				AppNamespacedName: types.AppNamespacedName{
-					APIVersion: appsv1.SchemeGroupVersion.String(),
-					Kind:       constant.KindDeployment,
-					Namespace:  "test-ns",
-					Name:       "test-name",
-				},
-				UID: types2.UID("a-b-c"),
-				APP: nil,
-			}
-
-			podAffinity := NewAutoPoolPodAffinity(podTopController)
-
-			podTopController.Kind = constant.KindStatefulSet
-			isMatchAutoPoolAffinity := IsMatchAutoPoolAffinity(podAffinity, podTopController)
-			Expect(isMatchAutoPoolAffinity).To(BeFalse())
-		})
-	})
-
-})
diff --git a/test/e2e/kruise/kruise_test.go b/test/e2e/kruise/kruise_test.go
index 3b2e8484a..facf558be 100644
--- a/test/e2e/kruise/kruise_test.go
+++ b/test/e2e/kruise/kruise_test.go
@@ -19,7 +19,7 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client"
 )
 
-var _ = PDescribe("Third party control:OpenKruise", Label("kruise"), func() {
+var _ = Describe("Third party control:OpenKruise", Label("kruise"), func() {
 	var namespace, kruiseCloneSetName, kruiseStatefulSetName, v4SubnetName, v6SubnetName, v4PoolName, v6PoolName string
 	var v4SubnetObject, v6SubnetObject *spiderpool.SpiderSubnet
 	var v4PoolObj, v6PoolObj *spiderpool.SpiderIPPool
diff --git a/vendor/github.com/containernetworking/plugins/pkg/testutils/bad_reader.go b/vendor/github.com/containernetworking/plugins/pkg/testutils/bad_reader.go
deleted file mode 100644
index f9d0aded5..000000000
--- a/vendor/github.com/containernetworking/plugins/pkg/testutils/bad_reader.go
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2016 CNI 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 testutils
-
-import "errors"
-
-// BadReader is an io.Reader which always errors
-type BadReader struct {
-	Error error
-}
-
-func (r *BadReader) Read(buffer []byte) (int, error) {
-	if r.Error != nil {
-		return 0, r.Error
-	}
-	return 0, errors.New("banana")
-}
-
-func (r *BadReader) Close() error {
-	return nil
-}
diff --git a/vendor/github.com/containernetworking/plugins/pkg/testutils/cmd.go b/vendor/github.com/containernetworking/plugins/pkg/testutils/cmd.go
deleted file mode 100644
index 304a1e9d8..000000000
--- a/vendor/github.com/containernetworking/plugins/pkg/testutils/cmd.go
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright 2016 CNI 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 testutils
-
-import (
-	"io"
-	"os"
-
-	"github.com/containernetworking/cni/pkg/skel"
-	"github.com/containernetworking/cni/pkg/types"
-	"github.com/containernetworking/cni/pkg/version"
-)
-
-func envCleanup() {
-	os.Unsetenv("CNI_COMMAND")
-	os.Unsetenv("CNI_PATH")
-	os.Unsetenv("CNI_NETNS")
-	os.Unsetenv("CNI_IFNAME")
-	os.Unsetenv("CNI_CONTAINERID")
-}
-
-func CmdAdd(cniNetns, cniContainerID, cniIfname string, conf []byte, f func() error) (types.Result, []byte, error) {
-	os.Setenv("CNI_COMMAND", "ADD")
-	os.Setenv("CNI_PATH", os.Getenv("PATH"))
-	os.Setenv("CNI_NETNS", cniNetns)
-	os.Setenv("CNI_IFNAME", cniIfname)
-	os.Setenv("CNI_CONTAINERID", cniContainerID)
-	defer envCleanup()
-
-	// Redirect stdout to capture plugin result
-	oldStdout := os.Stdout
-	r, w, err := os.Pipe()
-	if err != nil {
-		return nil, nil, err
-	}
-
-	os.Stdout = w
-	err = f()
-	w.Close()
-
-	var out []byte
-	if err == nil {
-		out, err = io.ReadAll(r)
-	}
-	os.Stdout = oldStdout
-
-	// Return errors after restoring stdout so Ginkgo will correctly
-	// emit verbose error information on stdout
-	if err != nil {
-		return nil, nil, err
-	}
-
-	// Plugin must return result in same version as specified in netconf
-	versionDecoder := &version.ConfigDecoder{}
-	confVersion, err := versionDecoder.Decode(conf)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	result, err := version.NewResult(confVersion, out)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	return result, out, nil
-}
-
-func CmdAddWithArgs(args *skel.CmdArgs, f func() error) (types.Result, []byte, error) {
-	return CmdAdd(args.Netns, args.ContainerID, args.IfName, args.StdinData, f)
-}
-
-func CmdCheck(cniNetns, cniContainerID, cniIfname string, conf []byte, f func() error) error {
-	os.Setenv("CNI_COMMAND", "CHECK")
-	os.Setenv("CNI_PATH", os.Getenv("PATH"))
-	os.Setenv("CNI_NETNS", cniNetns)
-	os.Setenv("CNI_IFNAME", cniIfname)
-	os.Setenv("CNI_CONTAINERID", cniContainerID)
-	defer envCleanup()
-
-	return f()
-}
-
-func CmdCheckWithArgs(args *skel.CmdArgs, f func() error) error {
-	return CmdCheck(args.Netns, args.ContainerID, args.IfName, args.StdinData, f)
-}
-
-func CmdDel(cniNetns, cniContainerID, cniIfname string, f func() error) error {
-	os.Setenv("CNI_COMMAND", "DEL")
-	os.Setenv("CNI_PATH", os.Getenv("PATH"))
-	os.Setenv("CNI_NETNS", cniNetns)
-	os.Setenv("CNI_IFNAME", cniIfname)
-	os.Setenv("CNI_CONTAINERID", cniContainerID)
-	defer envCleanup()
-
-	return f()
-}
-
-func CmdDelWithArgs(args *skel.CmdArgs, f func() error) error {
-	return CmdDel(args.Netns, args.ContainerID, args.IfName, f)
-}
diff --git a/vendor/github.com/containernetworking/plugins/pkg/testutils/dns.go b/vendor/github.com/containernetworking/plugins/pkg/testutils/dns.go
deleted file mode 100644
index bd0de0a81..000000000
--- a/vendor/github.com/containernetworking/plugins/pkg/testutils/dns.go
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2019 CNI 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 testutils
-
-import (
-	"fmt"
-	"os"
-	"strings"
-
-	"github.com/containernetworking/cni/pkg/types"
-)
-
-// TmpResolvConf will create a temporary file and write the provided DNS settings to
-// it in the resolv.conf format. It returns the path of the created temporary file or
-// an error if any occurs while creating/writing the file. It is the caller's
-// responsibility to remove the file.
-func TmpResolvConf(dnsConf types.DNS) (string, error) {
-	f, err := os.CreateTemp("", "cni_test_resolv.conf")
-	if err != nil {
-		return "", fmt.Errorf("failed to get temp file for CNI test resolv.conf: %v", err)
-	}
-	defer f.Close()
-
-	path := f.Name()
-	defer func() {
-		if err != nil {
-			os.RemoveAll(path)
-		}
-	}()
-
-	// see "man 5 resolv.conf" for the format of resolv.conf
-	var resolvConfLines []string
-	for _, nameserver := range dnsConf.Nameservers {
-		resolvConfLines = append(resolvConfLines, fmt.Sprintf("nameserver %s", nameserver))
-	}
-	resolvConfLines = append(resolvConfLines, fmt.Sprintf("domain %s", dnsConf.Domain))
-	resolvConfLines = append(resolvConfLines, fmt.Sprintf("search %s", strings.Join(dnsConf.Search, " ")))
-	resolvConfLines = append(resolvConfLines, fmt.Sprintf("options %s", strings.Join(dnsConf.Options, " ")))
-
-	resolvConf := strings.Join(resolvConfLines, "\n")
-	_, err = f.Write([]byte(resolvConf))
-	if err != nil {
-		return "", fmt.Errorf("failed to write temp resolv.conf for CNI test: %v", err)
-	}
-
-	return path, err
-}
diff --git a/vendor/github.com/containernetworking/plugins/pkg/testutils/netns_linux.go b/vendor/github.com/containernetworking/plugins/pkg/testutils/netns_linux.go
deleted file mode 100644
index b9683a3a4..000000000
--- a/vendor/github.com/containernetworking/plugins/pkg/testutils/netns_linux.go
+++ /dev/null
@@ -1,176 +0,0 @@
-// Copyright 2018 CNI 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 testutils
-
-import (
-	"crypto/rand"
-	"fmt"
-	"os"
-	"path"
-	"runtime"
-	"strings"
-	"sync"
-	"syscall"
-
-	"github.com/containernetworking/plugins/pkg/ns"
-	"golang.org/x/sys/unix"
-)
-
-func getNsRunDir() string {
-	xdgRuntimeDir := os.Getenv("XDG_RUNTIME_DIR")
-
-	/// If XDG_RUNTIME_DIR is set, check if the current user owns /var/run.  If
-	// the owner is different, we are most likely running in a user namespace.
-	// In that case use $XDG_RUNTIME_DIR/netns as runtime dir.
-	if xdgRuntimeDir != "" {
-		if s, err := os.Stat("/var/run"); err == nil {
-			st, ok := s.Sys().(*syscall.Stat_t)
-			if ok && int(st.Uid) != os.Geteuid() {
-				return path.Join(xdgRuntimeDir, "netns")
-			}
-		}
-	}
-
-	return "/var/run/netns"
-}
-
-// Creates a new persistent (bind-mounted) network namespace and returns an object
-// representing that namespace, without switching to it.
-func NewNS() (ns.NetNS, error) {
-
-	nsRunDir := getNsRunDir()
-
-	b := make([]byte, 16)
-	_, err := rand.Read(b)
-	if err != nil {
-		return nil, fmt.Errorf("failed to generate random netns name: %v", err)
-	}
-
-	// Create the directory for mounting network namespaces
-	// This needs to be a shared mountpoint in case it is mounted in to
-	// other namespaces (containers)
-	err = os.MkdirAll(nsRunDir, 0755)
-	if err != nil {
-		return nil, err
-	}
-
-	// Remount the namespace directory shared. This will fail if it is not
-	// already a mountpoint, so bind-mount it on to itself to "upgrade" it
-	// to a mountpoint.
-	err = unix.Mount("", nsRunDir, "none", unix.MS_SHARED|unix.MS_REC, "")
-	if err != nil {
-		if err != unix.EINVAL {
-			return nil, fmt.Errorf("mount --make-rshared %s failed: %q", nsRunDir, err)
-		}
-
-		// Recursively remount /var/run/netns on itself. The recursive flag is
-		// so that any existing netns bindmounts are carried over.
-		err = unix.Mount(nsRunDir, nsRunDir, "none", unix.MS_BIND|unix.MS_REC, "")
-		if err != nil {
-			return nil, fmt.Errorf("mount --rbind %s %s failed: %q", nsRunDir, nsRunDir, err)
-		}
-
-		// Now we can make it shared
-		err = unix.Mount("", nsRunDir, "none", unix.MS_SHARED|unix.MS_REC, "")
-		if err != nil {
-			return nil, fmt.Errorf("mount --make-rshared %s failed: %q", nsRunDir, err)
-		}
-
-	}
-
-	nsName := fmt.Sprintf("cnitest-%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
-
-	// create an empty file at the mount point
-	nsPath := path.Join(nsRunDir, nsName)
-	mountPointFd, err := os.Create(nsPath)
-	if err != nil {
-		return nil, err
-	}
-	mountPointFd.Close()
-
-	// Ensure the mount point is cleaned up on errors; if the namespace
-	// was successfully mounted this will have no effect because the file
-	// is in-use
-	defer os.RemoveAll(nsPath)
-
-	var wg sync.WaitGroup
-	wg.Add(1)
-
-	// do namespace work in a dedicated goroutine, so that we can safely
-	// Lock/Unlock OSThread without upsetting the lock/unlock state of
-	// the caller of this function
-	go (func() {
-		defer wg.Done()
-		runtime.LockOSThread()
-		// Don't unlock. By not unlocking, golang will kill the OS thread when the
-		// goroutine is done (for go1.10+)
-
-		var origNS ns.NetNS
-		origNS, err = ns.GetNS(getCurrentThreadNetNSPath())
-		if err != nil {
-			return
-		}
-		defer origNS.Close()
-
-		// create a new netns on the current thread
-		err = unix.Unshare(unix.CLONE_NEWNET)
-		if err != nil {
-			return
-		}
-
-		// Put this thread back to the orig ns, since it might get reused (pre go1.10)
-		defer origNS.Set()
-
-		// bind mount the netns from the current thread (from /proc) onto the
-		// mount point. This causes the namespace to persist, even when there
-		// are no threads in the ns.
-		err = unix.Mount(getCurrentThreadNetNSPath(), nsPath, "none", unix.MS_BIND, "")
-		if err != nil {
-			err = fmt.Errorf("failed to bind mount ns at %s: %v", nsPath, err)
-		}
-	})()
-	wg.Wait()
-
-	if err != nil {
-		return nil, fmt.Errorf("failed to create namespace: %v", err)
-	}
-
-	return ns.GetNS(nsPath)
-}
-
-// UnmountNS unmounts the NS held by the netns object
-func UnmountNS(ns ns.NetNS) error {
-	nsPath := ns.Path()
-	// Only unmount if it's been bind-mounted (don't touch namespaces in /proc...)
-	if strings.HasPrefix(nsPath, getNsRunDir()) {
-		if err := unix.Unmount(nsPath, 0); err != nil {
-			return fmt.Errorf("failed to unmount NS: at %s: %v", nsPath, err)
-		}
-
-		if err := os.Remove(nsPath); err != nil {
-			return fmt.Errorf("failed to remove ns path %s: %v", nsPath, err)
-		}
-	}
-
-	return nil
-}
-
-// getCurrentThreadNetNSPath copied from pkg/ns
-func getCurrentThreadNetNSPath() string {
-	// /proc/self/ns/net returns the namespace of the main thread, not
-	// of whatever thread this goroutine is running on.  Make sure we
-	// use the thread's net namespace since the thread is switching around
-	return fmt.Sprintf("/proc/%d/task/%d/ns/net", os.Getpid(), unix.Gettid())
-}
diff --git a/vendor/github.com/containernetworking/plugins/pkg/testutils/ping.go b/vendor/github.com/containernetworking/plugins/pkg/testutils/ping.go
deleted file mode 100644
index 8c47c3d7a..000000000
--- a/vendor/github.com/containernetworking/plugins/pkg/testutils/ping.go
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright 2017 CNI 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 testutils
-
-import (
-	"bytes"
-	"fmt"
-	"net"
-	"os/exec"
-	"strconv"
-	"syscall"
-)
-
-// Ping shells out to the `ping` command. Returns nil if successful.
-func Ping(saddr, daddr string, timeoutSec int) error {
-	ip := net.ParseIP(saddr)
-	if ip == nil {
-		return fmt.Errorf("failed to parse IP %q", saddr)
-	}
-
-	bin := "ping6"
-	if ip.To4() != nil {
-		bin = "ping"
-	}
-
-	args := []string{
-		"-c", "1",
-		"-W", strconv.Itoa(timeoutSec),
-		"-I", saddr,
-		daddr,
-	}
-
-	cmd := exec.Command(bin, args...)
-	var stderr bytes.Buffer
-	cmd.Stderr = &stderr
-
-	if err := cmd.Run(); err != nil {
-		switch e := err.(type) {
-		case *exec.ExitError:
-			return fmt.Errorf("%v exit status %d: %s",
-				args, e.Sys().(syscall.WaitStatus).ExitStatus(),
-				stderr.String())
-		default:
-			return err
-		}
-	}
-
-	return nil
-}
diff --git a/vendor/github.com/containernetworking/plugins/pkg/testutils/testing.go b/vendor/github.com/containernetworking/plugins/pkg/testutils/testing.go
deleted file mode 100644
index 9444a8b2d..000000000
--- a/vendor/github.com/containernetworking/plugins/pkg/testutils/testing.go
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2016 CNI 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 testutils
-
-import (
-	"github.com/containernetworking/cni/pkg/version"
-)
-
-// AllSpecVersions contains all CNI spec version numbers
-var AllSpecVersions = [...]string{"0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0"}
-
-// SpecVersionHasIPVersion returns true if the given CNI specification version
-// includes the "version" field in the IP address elements
-func SpecVersionHasIPVersion(ver string) bool {
-	for _, i := range []string{"0.3.0", "0.3.1", "0.4.0"} {
-		if ver == i {
-			return true
-		}
-	}
-	return false
-}
-
-// SpecVersionHasCHECK returns true if the given CNI specification version
-// supports the CHECK command
-func SpecVersionHasCHECK(ver string) bool {
-	ok, _ := version.GreaterThanOrEqualTo(ver, "0.4.0")
-	return ok
-}
-
-// SpecVersionHasChaining returns true if the given CNI specification version
-// supports plugin chaining
-func SpecVersionHasChaining(ver string) bool {
-	ok, _ := version.GreaterThanOrEqualTo(ver, "0.3.0")
-	return ok
-}
-
-// SpecVersionHasMultipleIPs returns true if the given CNI specification version
-// supports more than one IP address of each family
-func SpecVersionHasMultipleIPs(ver string) bool {
-	ok, _ := version.GreaterThanOrEqualTo(ver, "0.3.0")
-	return ok
-}
diff --git a/vendor/github.com/onsi/gomega/ghttp/test_server.go b/vendor/github.com/onsi/gomega/ghttp/test_server.go
deleted file mode 100644
index 383573bd9..000000000
--- a/vendor/github.com/onsi/gomega/ghttp/test_server.go
+++ /dev/null
@@ -1,425 +0,0 @@
-/*
-Package ghttp supports testing HTTP clients by providing a test server (simply a thin wrapper around httptest's server) that supports
-registering multiple handlers.  Incoming requests are not routed between the different handlers
-- rather it is merely the order of the handlers that matters.  The first request is handled by the first
-registered handler, the second request by the second handler, etc.
-
-The intent here is to have each handler *verify* that the incoming request is valid.  To accomplish, ghttp
-also provides a collection of bite-size handlers that each perform one aspect of request verification.  These can
-be composed together and registered with a ghttp server.  The result is an expressive language for describing
-the requests generated by the client under test.
-
-Here's a simple example, note that the server handler is only defined in one BeforeEach and then modified, as required, by the nested BeforeEaches.
-A more comprehensive example is available at https://onsi.github.io/gomega/#_testing_http_clients
-
-	var _ = Describe("A Sprockets Client", func() {
-		var server *ghttp.Server
-		var client *SprocketClient
-		BeforeEach(func() {
-			server = ghttp.NewServer()
-			client = NewSprocketClient(server.URL(), "skywalker", "tk427")
-		})
-
-		AfterEach(func() {
-			server.Close()
-		})
-
-		Describe("fetching sprockets", func() {
-			var statusCode int
-			var sprockets []Sprocket
-			BeforeEach(func() {
-				statusCode = http.StatusOK
-				sprockets = []Sprocket{}
-				server.AppendHandlers(ghttp.CombineHandlers(
-					ghttp.VerifyRequest("GET", "/sprockets"),
-					ghttp.VerifyBasicAuth("skywalker", "tk427"),
-					ghttp.RespondWithJSONEncodedPtr(&statusCode, &sprockets),
-				))
-			})
-
-			When("requesting all sprockets", func() {
-				When("the response is successful", func() {
-					BeforeEach(func() {
-						sprockets = []Sprocket{
-							NewSprocket("Alfalfa"),
-							NewSprocket("Banana"),
-						}
-					})
-
-					It("should return the returned sprockets", func() {
-						Expect(client.Sprockets()).Should(Equal(sprockets))
-					})
-				})
-
-				When("the response is missing", func() {
-					BeforeEach(func() {
-						statusCode = http.StatusNotFound
-					})
-
-					It("should return an empty list of sprockets", func() {
-						Expect(client.Sprockets()).Should(BeEmpty())
-					})
-				})
-
-				When("the response fails to authenticate", func() {
-					BeforeEach(func() {
-						statusCode = http.StatusUnauthorized
-					})
-
-					It("should return an AuthenticationError error", func() {
-						sprockets, err := client.Sprockets()
-						Expect(sprockets).Should(BeEmpty())
-						Expect(err).Should(MatchError(AuthenticationError))
-					})
-				})
-
-				When("the response is a server failure", func() {
-					BeforeEach(func() {
-						statusCode = http.StatusInternalServerError
-					})
-
-					It("should return an InternalError error", func() {
-						sprockets, err := client.Sprockets()
-						Expect(sprockets).Should(BeEmpty())
-						Expect(err).Should(MatchError(InternalError))
-					})
-				})
-			})
-
-			When("requesting some sprockets", func() {
-				BeforeEach(func() {
-					sprockets = []Sprocket{
-						NewSprocket("Alfalfa"),
-						NewSprocket("Banana"),
-					}
-
-					server.WrapHandler(0, ghttp.VerifyRequest("GET", "/sprockets", "filter=FOOD"))
-				})
-
-				It("should make the request with a filter", func() {
-					Expect(client.Sprockets("food")).Should(Equal(sprockets))
-				})
-			})
-		})
-	})
-*/
-
-// untested sections: 5
-
-package ghttp
-
-import (
-	"fmt"
-	"io"
-	"net/http"
-	"net/http/httptest"
-	"net/http/httputil"
-	"reflect"
-	"regexp"
-	"strings"
-	"sync"
-
-	. "github.com/onsi/gomega"
-	"github.com/onsi/gomega/internal/gutil"
-)
-
-func new() *Server {
-	return &Server{
-		AllowUnhandledRequests:     false,
-		UnhandledRequestStatusCode: http.StatusInternalServerError,
-		rwMutex:                    &sync.RWMutex{},
-	}
-}
-
-type routedHandler struct {
-	method     string
-	pathRegexp *regexp.Regexp
-	path       string
-	handler    http.HandlerFunc
-}
-
-// NewServer returns a new `*ghttp.Server` that wraps an `httptest` server.  The server is started automatically.
-func NewServer() *Server {
-	s := new()
-	s.HTTPTestServer = httptest.NewServer(s)
-	return s
-}
-
-// NewUnstartedServer return a new, unstarted, `*ghttp.Server`.  Useful for specifying a custom listener on `server.HTTPTestServer`.
-func NewUnstartedServer() *Server {
-	s := new()
-	s.HTTPTestServer = httptest.NewUnstartedServer(s)
-	return s
-}
-
-// NewTLSServer returns a new `*ghttp.Server` that wraps an `httptest` TLS server.  The server is started automatically.
-func NewTLSServer() *Server {
-	s := new()
-	s.HTTPTestServer = httptest.NewTLSServer(s)
-	return s
-}
-
-type Server struct {
-	//The underlying httptest server
-	HTTPTestServer *httptest.Server
-
-	//Defaults to false.  If set to true, the Server will allow more requests than there are registered handlers.
-	//Direct use of this property is deprecated and is likely to be removed, use GetAllowUnhandledRequests and SetAllowUnhandledRequests instead.
-	AllowUnhandledRequests bool
-
-	//The status code returned when receiving an unhandled request.
-	//Defaults to http.StatusInternalServerError.
-	//Only applies if AllowUnhandledRequests is true
-	//Direct use of this property is deprecated and is likely to be removed, use GetUnhandledRequestStatusCode and SetUnhandledRequestStatusCode instead.
-	UnhandledRequestStatusCode int
-
-	//If provided, ghttp will log about each request received to the provided io.Writer
-	//Defaults to nil
-	//If you're using Ginkgo, set this to GinkgoWriter to get improved output during failures
-	Writer io.Writer
-
-	receivedRequests []*http.Request
-	requestHandlers  []http.HandlerFunc
-	routedHandlers   []routedHandler
-
-	rwMutex *sync.RWMutex
-	calls   int
-}
-
-//Start() starts an unstarted ghttp server.  It is a catastrophic error to call Start more than once (thanks, httptest).
-func (s *Server) Start() {
-	s.HTTPTestServer.Start()
-}
-
-//URL() returns a url that will hit the server
-func (s *Server) URL() string {
-	s.rwMutex.RLock()
-	defer s.rwMutex.RUnlock()
-	return s.HTTPTestServer.URL
-}
-
-//Addr() returns the address on which the server is listening.
-func (s *Server) Addr() string {
-	s.rwMutex.RLock()
-	defer s.rwMutex.RUnlock()
-	return s.HTTPTestServer.Listener.Addr().String()
-}
-
-//Close() should be called at the end of each test.  It spins down and cleans up the test server.
-func (s *Server) Close() {
-	s.rwMutex.Lock()
-	server := s.HTTPTestServer
-	s.HTTPTestServer = nil
-	s.rwMutex.Unlock()
-
-	if server != nil {
-		server.Close()
-	}
-}
-
-//ServeHTTP() makes Server an http.Handler
-//When the server receives a request it handles the request in the following order:
-//
-//1. If the request matches a handler registered with RouteToHandler, that handler is called.
-//2. Otherwise, if there are handlers registered via AppendHandlers, those handlers are called in order.
-//3. If all registered handlers have been called then:
-//   a) If AllowUnhandledRequests is set to true, the request will be handled with response code of UnhandledRequestStatusCode
-//   b) If AllowUnhandledRequests is false, the request will not be handled and the current test will be marked as failed.
-func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
-	s.rwMutex.Lock()
-	defer func() {
-		e := recover()
-		if e != nil {
-			w.WriteHeader(http.StatusInternalServerError)
-		}
-
-		//If the handler panics GHTTP will silently succeed.  This is bad™.
-		//To catch this case we need to fail the test if the handler has panicked.
-		//However, if the handler is panicking because Ginkgo's causing it to panic (i.e. an assertion failed)
-		//then we shouldn't double-report the error as this will confuse people.
-
-		//So: step 1, if this is a Ginkgo panic - do nothing, Ginkgo's aware of the failure
-		eAsString, ok := e.(string)
-		if ok && strings.Contains(eAsString, "defer GinkgoRecover()") {
-			return
-		}
-
-		//If we're here, we have to do step 2: assert that the error is nil.  This assertion will
-		//allow us to fail the test suite (note: we can't call Fail since Gomega is not allowed to import Ginkgo).
-		//Since a failed assertion throws a panic, and we are likely in a goroutine, we need to defer within our defer!
-		defer func() {
-			recover()
-		}()
-		Expect(e).Should(BeNil(), "Handler Panicked")
-	}()
-
-	if s.Writer != nil {
-		s.Writer.Write([]byte(fmt.Sprintf("GHTTP Received Request: %s - %s\n", req.Method, req.URL)))
-	}
-
-	s.receivedRequests = append(s.receivedRequests, req)
-	if routedHandler, ok := s.handlerForRoute(req.Method, req.URL.Path); ok {
-		s.rwMutex.Unlock()
-		routedHandler(w, req)
-	} else if s.calls < len(s.requestHandlers) {
-		h := s.requestHandlers[s.calls]
-		s.calls++
-		s.rwMutex.Unlock()
-		h(w, req)
-	} else {
-		s.rwMutex.Unlock()
-		if s.GetAllowUnhandledRequests() {
-			gutil.ReadAll(req.Body)
-			req.Body.Close()
-			w.WriteHeader(s.GetUnhandledRequestStatusCode())
-		} else {
-			formatted, err := httputil.DumpRequest(req, true)
-			Expect(err).NotTo(HaveOccurred(), "Encountered error while dumping HTTP request")
-			Expect(string(formatted)).Should(BeNil(), "Received Unhandled Request")
-		}
-	}
-}
-
-//ReceivedRequests is an array containing all requests received by the server (both handled and unhandled requests)
-func (s *Server) ReceivedRequests() []*http.Request {
-	s.rwMutex.RLock()
-	defer s.rwMutex.RUnlock()
-
-	return s.receivedRequests
-}
-
-//RouteToHandler can be used to register handlers that will always handle requests that match
-//the passed in method and path.
-//
-//The path may be either a string object or a *regexp.Regexp.
-func (s *Server) RouteToHandler(method string, path interface{}, handler http.HandlerFunc) {
-	s.rwMutex.Lock()
-	defer s.rwMutex.Unlock()
-
-	rh := routedHandler{
-		method:  method,
-		handler: handler,
-	}
-
-	switch p := path.(type) {
-	case *regexp.Regexp:
-		rh.pathRegexp = p
-	case string:
-		rh.path = p
-	default:
-		panic("path must be a string or a regular expression")
-	}
-
-	for i, existingRH := range s.routedHandlers {
-		if existingRH.method == method &&
-			reflect.DeepEqual(existingRH.pathRegexp, rh.pathRegexp) &&
-			existingRH.path == rh.path {
-			s.routedHandlers[i] = rh
-			return
-		}
-	}
-	s.routedHandlers = append(s.routedHandlers, rh)
-}
-
-func (s *Server) handlerForRoute(method string, path string) (http.HandlerFunc, bool) {
-	for _, rh := range s.routedHandlers {
-		if rh.method == method {
-			if rh.pathRegexp != nil {
-				if rh.pathRegexp.Match([]byte(path)) {
-					return rh.handler, true
-				}
-			} else if rh.path == path {
-				return rh.handler, true
-			}
-		}
-	}
-
-	return nil, false
-}
-
-//AppendHandlers will appends http.HandlerFuncs to the server's list of registered handlers.  The first incoming request is handled by the first handler, the second by the second, etc...
-func (s *Server) AppendHandlers(handlers ...http.HandlerFunc) {
-	s.rwMutex.Lock()
-	defer s.rwMutex.Unlock()
-
-	s.requestHandlers = append(s.requestHandlers, handlers...)
-}
-
-//SetHandler overrides the registered handler at the passed in index with the passed in handler
-//This is useful, for example, when a server has been set up in a shared context, but must be tweaked
-//for a particular test.
-func (s *Server) SetHandler(index int, handler http.HandlerFunc) {
-	s.rwMutex.Lock()
-	defer s.rwMutex.Unlock()
-
-	s.requestHandlers[index] = handler
-}
-
-//GetHandler returns the handler registered at the passed in index.
-func (s *Server) GetHandler(index int) http.HandlerFunc {
-	s.rwMutex.RLock()
-	defer s.rwMutex.RUnlock()
-
-	return s.requestHandlers[index]
-}
-
-func (s *Server) Reset() {
-	s.rwMutex.Lock()
-	defer s.rwMutex.Unlock()
-
-	s.HTTPTestServer.CloseClientConnections()
-	s.calls = 0
-	s.receivedRequests = nil
-	s.requestHandlers = nil
-	s.routedHandlers = nil
-}
-
-//WrapHandler combines the passed in handler with the handler registered at the passed in index.
-//This is useful, for example, when a server has been set up in a shared context but must be tweaked
-//for a particular test.
-//
-//If the currently registered handler is A, and the new passed in handler is B then
-//WrapHandler will generate a new handler that first calls A, then calls B, and assign it to index
-func (s *Server) WrapHandler(index int, handler http.HandlerFunc) {
-	existingHandler := s.GetHandler(index)
-	s.SetHandler(index, CombineHandlers(existingHandler, handler))
-}
-
-func (s *Server) CloseClientConnections() {
-	s.rwMutex.Lock()
-	defer s.rwMutex.Unlock()
-
-	s.HTTPTestServer.CloseClientConnections()
-}
-
-//SetAllowUnhandledRequests enables the server to accept unhandled requests.
-func (s *Server) SetAllowUnhandledRequests(allowUnhandledRequests bool) {
-	s.rwMutex.Lock()
-	defer s.rwMutex.Unlock()
-
-	s.AllowUnhandledRequests = allowUnhandledRequests
-}
-
-//GetAllowUnhandledRequests returns true if the server accepts unhandled requests.
-func (s *Server) GetAllowUnhandledRequests() bool {
-	s.rwMutex.RLock()
-	defer s.rwMutex.RUnlock()
-
-	return s.AllowUnhandledRequests
-}
-
-//SetUnhandledRequestStatusCode status code to be returned when the server receives unhandled requests
-func (s *Server) SetUnhandledRequestStatusCode(statusCode int) {
-	s.rwMutex.Lock()
-	defer s.rwMutex.Unlock()
-
-	s.UnhandledRequestStatusCode = statusCode
-}
-
-//GetUnhandledRequestStatusCode returns the current status code being returned for unhandled requests
-func (s *Server) GetUnhandledRequestStatusCode() int {
-	s.rwMutex.RLock()
-	defer s.rwMutex.RUnlock()
-
-	return s.UnhandledRequestStatusCode
-}