Repository: spidernet-io/spiderpool
Pull request #3262 (2024-02-29 08:33:59)

feature: support wildcard match for IPPool
With the following ways of specifying IPPools(Pod Annotation, Namespace Annotation, CNI configuration file), you can also use wildcards `*`, `?` and `[]` to match the desired IPPools. 

For example: 
`ipam.spidernet.io/ippool: '{"ipv4": ["demo-v4-ippool1", "backup-ipv4*"]}'`

Signed-off-by: Icarus9913 [icaruswu66@qq.com](mailto:icaruswu66@qq.com)

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/spidernet-io/spiderpool/pull/3262?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spidernet-io) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 81.27%. Comparing base [(`3556e10`)](https://app.codecov.io/gh/spidernet-io/spiderpool/commit/3556e102c9ab346984059d986bb09c9b11d02691?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spidernet-io) to head [(`4b73774`)](https://app.codecov.io/gh/spidernet-io/spiderpool/pull/3262?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spidernet-io).

<details><summary>Additional details and impacted files</summary>


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

```diff
@@            Coverage Diff             @@
##             main    #3262      +/-   ##
==========================================
+ Coverage   81.12%   81.27%   +0.15%     
==========================================
  Files          50       50              
  Lines        5372     5416      +44     
==========================================
+ Hits         4358     4402      +44     
  Misses        856      856              
  Partials      158      158              
```

| [Flag](https://app.codecov.io/gh/spidernet-io/spiderpool/pull/3262/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spidernet-io) | Coverage Δ | |
|---|---|---|
| [unittests](https://app.codecov.io/gh/spidernet-io/spiderpool/pull/3262/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spidernet-io) | `81.27% <100.00%> (+0.15%)` | :arrow_up: |

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

| [Files](https://app.codecov.io/gh/spidernet-io/spiderpool/pull/3262?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spidernet-io) | Coverage Δ | |
|---|---|---|
| [pkg/ippoolmanager/ippool\_manager.go](https://app.codecov.io/gh/spidernet-io/spiderpool/pull/3262?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spidernet-io#diff-cGtnL2lwcG9vbG1hbmFnZXIvaXBwb29sX21hbmFnZXIuZ28=) | `87.32% <100.00%> (+1.44%)` | :arrow_up: |
| [pkg/ippoolmanager/utils.go](https://app.codecov.io/gh/spidernet-io/spiderpool/pull/3262?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spidernet-io#diff-cGtnL2lwcG9vbG1hbmFnZXIvdXRpbHMuZ28=) | `94.28% <100.00%> (+0.95%)` | :arrow_up: |
| [pkg/reservedipmanager/reservedip\_manager.go](https://app.codecov.io/gh/spidernet-io/spiderpool/pull/3262?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spidernet-io#diff-cGtnL3Jlc2VydmVkaXBtYW5hZ2VyL3Jlc2VydmVkaXBfbWFuYWdlci5nbw==) | `100.00% <100.00%> (ø)` | |

</details>

Patch:
diff --git a/charts/spiderpool/README.md b/charts/spiderpool/README.md
index a0c435568..0d18b25f4 100644
--- a/charts/spiderpool/README.md
+++ b/charts/spiderpool/README.md
@@ -187,7 +187,6 @@ helm install spiderpool spiderpool/spiderpool --wait --namespace kube-system \
 | `rdma.rdmaSharedDevicePlugin.deviceConfig.rdmaHcaMax`             | rdma Hca Max                                            | `500`                                  |
 | `rdma.rdmaSharedDevicePlugin.deviceConfig.vendors`                | rdma device vendors, default to mellanox device         | `15b3`                                 |
 | `rdma.rdmaSharedDevicePlugin.deviceConfig.deviceIDs`              | rdma device IDs, default to mellanox device             | `1017`                                 |
-| `rdma.rdmaSharedDevicePlugin.deviceConfig.ifNames`                | rdma target device name, default to empty list.         | `[]`                                   |
 
 ### multus parameters
 
diff --git a/charts/spiderpool/templates/rdma-shared-dp/rdma-shared-dp.yaml b/charts/spiderpool/templates/rdma-shared-dp/rdma-shared-dp.yaml
index c28c2de3f..52958e7df 100644
--- a/charts/spiderpool/templates/rdma-shared-dp/rdma-shared-dp.yaml
+++ b/charts/spiderpool/templates/rdma-shared-dp/rdma-shared-dp.yaml
@@ -22,8 +22,7 @@ data:
              "rdmaHcaMax": {{ .Values.rdma.rdmaSharedDevicePlugin.deviceConfig.rdmaHcaMax }},
              "selectors": {
                "vendors": [{{ .Values.rdma.rdmaSharedDevicePlugin.deviceConfig.vendors | quote }}],
-               "deviceIDs": [{{ .Values.rdma.rdmaSharedDevicePlugin.deviceConfig.deviceIDs | quote }}],
-               "ifNames": {{ toJson .Values.rdma.rdmaSharedDevicePlugin.deviceConfig.ifNames }}
+               "deviceIDs": [{{ .Values.rdma.rdmaSharedDevicePlugin.deviceConfig.deviceIDs | quote }}]
               }
         }]
     }
diff --git a/charts/spiderpool/values.yaml b/charts/spiderpool/values.yaml
index 14a868d79..4ba75a49e 100644
--- a/charts/spiderpool/values.yaml
+++ b/charts/spiderpool/values.yaml
@@ -197,9 +197,6 @@ rdma:
       ## @param rdma.rdmaSharedDevicePlugin.deviceConfig.deviceIDs rdma device IDs, default to mellanox device
       deviceIDs: "1017"
 
-      ## @param rdma.rdmaSharedDevicePlugin.deviceConfig.ifNames rdma target device name, default to empty list.
-      ifNames: []
-
 ## @section multus parameters
 ##
 multus:
diff --git a/cmd/spiderpool-agent/cmd/crd_manager.go b/cmd/spiderpool-agent/cmd/crd_manager.go
index 2e713ba54..8a37619aa 100644
--- a/cmd/spiderpool-agent/cmd/crd_manager.go
+++ b/cmd/spiderpool-agent/cmd/crd_manager.go
@@ -16,6 +16,7 @@ import (
 	controllerruntimelog "sigs.k8s.io/controller-runtime/pkg/log"
 	metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
 
+	"github.com/spidernet-io/spiderpool/pkg/constant"
 	spiderpoolv2beta1 "github.com/spidernet-io/spiderpool/pkg/k8s/apis/spiderpool.spidernet.io/v2beta1"
 )
 
@@ -46,14 +47,21 @@ func newCRDManager() (ctrl.Manager, error) {
 		return nil, err
 	}
 
-	if err := mgr.GetFieldIndexer().IndexField(agentContext.InnerCtx, &spiderpoolv2beta1.SpiderIPPool{}, "spec.default", func(raw client.Object) []string {
+	if err := mgr.GetFieldIndexer().IndexField(agentContext.InnerCtx, &spiderpoolv2beta1.SpiderIPPool{}, constant.SpecDefaultField, func(raw client.Object) []string {
 		ipPool := raw.(*spiderpoolv2beta1.SpiderIPPool)
 		return []string{strconv.FormatBool(*ipPool.Spec.Default)}
 	}); err != nil {
 		return nil, err
 	}
 
-	if err := mgr.GetFieldIndexer().IndexField(agentContext.InnerCtx, &spiderpoolv2beta1.SpiderReservedIP{}, "spec.ipVersion", func(raw client.Object) []string {
+	if err := mgr.GetFieldIndexer().IndexField(agentContext.InnerCtx, &spiderpoolv2beta1.SpiderIPPool{}, constant.SpecIPVersionField, func(raw client.Object) []string {
+		ipPool := raw.(*spiderpoolv2beta1.SpiderIPPool)
+		return []string{strconv.FormatInt(*ipPool.Spec.IPVersion, 10)}
+	}); err != nil {
+		return nil, err
+	}
+
+	if err := mgr.GetFieldIndexer().IndexField(agentContext.InnerCtx, &spiderpoolv2beta1.SpiderReservedIP{}, constant.SpecIPVersionField, func(raw client.Object) []string {
 		reservedIP := raw.(*spiderpoolv2beta1.SpiderReservedIP)
 		return []string{strconv.FormatInt(*reservedIP.Spec.IPVersion, 10)}
 	}); err != nil {
diff --git a/cmd/spiderpool-controller/cmd/crd_manager.go b/cmd/spiderpool-controller/cmd/crd_manager.go
index 95c62e4af..c998ca4e7 100644
--- a/cmd/spiderpool-controller/cmd/crd_manager.go
+++ b/cmd/spiderpool-controller/cmd/crd_manager.go
@@ -65,14 +65,14 @@ func newCRDManager() (ctrl.Manager, error) {
 		return nil, err
 	}
 
-	if err := mgr.GetFieldIndexer().IndexField(controllerContext.InnerCtx, &spiderpoolv2beta1.SpiderIPPool{}, "spec.default", func(raw client.Object) []string {
+	if err := mgr.GetFieldIndexer().IndexField(controllerContext.InnerCtx, &spiderpoolv2beta1.SpiderIPPool{}, constant.SpecDefaultField, func(raw client.Object) []string {
 		ipPool := raw.(*spiderpoolv2beta1.SpiderIPPool)
 		return []string{strconv.FormatBool(*ipPool.Spec.Default)}
 	}); err != nil {
 		return nil, err
 	}
 
-	if err := mgr.GetFieldIndexer().IndexField(controllerContext.InnerCtx, &spiderpoolv2beta1.SpiderReservedIP{}, "spec.ipVersion", func(raw client.Object) []string {
+	if err := mgr.GetFieldIndexer().IndexField(controllerContext.InnerCtx, &spiderpoolv2beta1.SpiderReservedIP{}, constant.SpecIPVersionField, func(raw client.Object) []string {
 		reservedIP := raw.(*spiderpoolv2beta1.SpiderReservedIP)
 		return []string{strconv.FormatInt(*reservedIP.Spec.IPVersion, 10)}
 	}); err != nil {
diff --git a/docs/concepts/coordinator.md b/docs/concepts/coordinator.md
index fc6a251c6..3b63c48e9 100644
--- a/docs/concepts/coordinator.md
+++ b/docs/concepts/coordinator.md
@@ -155,7 +155,7 @@ spec:
 
 ## Automatically get the CIDR of a clustered Service
 
-Kubernetes 1.29 starts to support configuring the CIDR of a clustered Service as a ServiceCIDR resource, for more information refer to [KEP 1880](https://github.com/kubernetes/enhancements/blob/master/keps/ sig-network/1880-multiple-service-cidrs/README.md). If your cluster supports ServiceCIDR, the Spiderpool-controller component automatically listens for changes to the ServiceCIDR resource and automatically updates the Service subnet information it reads into the Status of the Spidercoordinator.
+Kubernetes 1.29 starts to support configuring the CIDR of a clustered Service as a ServiceCIDR resource, for more information refer to [KEP 1880](https://github.com/kubernetes/enhancements/blob/master/keps/sig-network/1880-multiple-service-cidrs/README.md). If your cluster supports ServiceCIDR, the Spiderpool-controller component automatically listens for changes to the ServiceCIDR resource and automatically updates the Service subnet information it reads into the Status of the Spidercoordinator.
 
 ```shell
 ~# kubectl get servicecidr kubernetes -o yaml
diff --git a/docs/usage/spider-ippool-zh_CN.md b/docs/usage/spider-ippool-zh_CN.md
index 2399419ac..b6bac07a3 100644
--- a/docs/usage/spider-ippool-zh_CN.md
+++ b/docs/usage/spider-ippool-zh_CN.md
@@ -55,17 +55,24 @@ spec:
 
 ### 指定 IPPool 为应用分配 IP 地址
 
-> 对于以下指定使用 SpiderIPPool 规则的优先级，请参考 [IP 候选池规则](./../concepts/ipam-des-zh_CN.md#获取候选池)
+该功能有： `使用 Pod Annotation 指定使用 IP 池`、`使用 Namespace 注解指定池`、`使用 CNI 配置文件指定池` 和 `为 SpiderIPPool 设置集群默认级别`一共4种使用方案。
+
+> 对于这4种指定使用 SpiderIPPool 规则的优先级，请参考 [IP 候选池规则](./../concepts/ipam-des-zh_CN.md#获取候选池)
+> 额外，指定 IP 池的方式(Pod Annotation, Namespace 注解, CNI 配置文件)，还可使用通配符 `*`, `?` 和 `[]` 来匹配期望的 IP 池。如: ipam.spidernet.io/ippool: '{"ipv4": ["demo-v4-ippool1", "backup-ipv4*"]}'。
+>
+>   1. '*': 匹配零个或多个字符。例如，"ab" 可以匹配 "ab"、"abc"、"abcd"等等。
+>   2. '?': 匹配一个单独的字符。例如，"a?c" 可以匹配 "abc"、"adc"、"axc"等等。
+>   3. '[]': 匹配指定范围内的一个字符。您可以在方括号内指定字符的选择，或者使用连字符指定字符范围。例如，"[abc]" 可以匹配 "a"、"b"、"c"中的任意一个字符。
 
 #### 使用 Pod Annotation 指定使用 IP 池
 
-我们可借助注解 `ipam.spidernet.io/ippool` 或 `ipam.spidernet.io/ippools` 标记在 Pod 的 Annotation上来指定 Pod 使用哪些 IP 池, 注解 `ipam.spidernet.io/ippools` 多用于多网卡指定。此外我们可以指定多个 IP 池以供备选，当某个池的 IP 被用完后，可继续从你指定的其他池中分配地址。
+可借助注解 `ipam.spidernet.io/ippool` 或 `ipam.spidernet.io/ippools` 标记在 Pod 的 Annotation上来指定 Pod 使用哪些 IP 池, 注解 `ipam.spidernet.io/ippools` 多用于多网卡指定。此外还可以指定多个 IP 池以供备选，当某个池的 IP 被用完后，可继续从你指定的其他池中分配地址。
 
 ```yaml
 ipam.spidernet.io/ippool: |-
   {
-    "ipv4": ["demo-v4-ippool1", "backup-ipv4-ippool"],
-    "ipv6": ["demo-v6-ippool1", "backup-ipv6-ippool"]
+    "ipv4": ["demo-v4-ippool1", "backup-ipv4-ippool", "wildcard-v4?"],
+    "ipv6": ["demo-v6-ippool1", "backup-ipv6-ippool", "wildcard-v6*"]
   }
 ```
 
@@ -76,11 +83,11 @@ ipam.spidernet.io/ippool: |-
 ```yaml
 ipam.spidernet.io/ippools: |-
   [{
-      "ipv4": ["demo-v4-ippool1"],
-      "ipv6": ["demo-v6-ippool1"],
+      "ipv4": ["demo-v4-ippool1", "wildcard-v4-ippool[123]"],
+      "ipv6": ["demo-v6-ippool1", "wildcard-v6-ippool[123]"]
    },{
-      "ipv4": ["demo-v4-ippool2"],
-      "ipv6": ["demo-v6-ippool2"],
+      "ipv4": ["demo-v4-ippool2", "wildcard-v4-ippool[456]"],
+      "ipv6": ["demo-v6-ippool2", "wildcard-v6-ippool[456]"],
       "cleangateway": true
   }]
 ```
@@ -89,13 +96,13 @@ ipam.spidernet.io/ippools: |-
 ipam.spidernet.io/ippools: |-
   [{
       "interface": "eth0",
-      "ipv4": ["demo-v4-ippool1"],
-      "ipv6": ["demo-v6-ippool1"],
+      "ipv4": ["demo-v4-ippool1", "wildcard-v4-ippool[123]"],
+      "ipv6": ["demo-v6-ippool1", "wildcard-v6-ippool[123]"],
       "cleangateway": true
    },{
       "interface": "net1",
-      "ipv4": ["demo-v4-ippool2"],
-      "ipv6": ["demo-v6-ippool2"],
+      "ipv4": ["demo-v4-ippool2", "wildcard-v4-ippool[456]"],
+      "ipv6": ["demo-v6-ippool2", "wildcard-v6-ippool[456]"],
       "cleangateway": false
   }]
 ```
@@ -112,8 +119,8 @@ apiVersion: v1
 kind: Namespace
 metadata:
   annotations:
-    ipam.spidernet.io/default-ipv4-ippool: '["ns-v4-ippool1","ns-v4-ippool2"]'
-    ipam.spidernet.io/default-ipv6-ippool: '["ns-v6-ippool1","ns-v6-ippool2"]'
+    ipam.spidernet.io/default-ipv4-ippool: '["ns-v4-ippool1", "ns-v4-ippool2", "wildcard-v4*"]'
+    ipam.spidernet.io/default-ipv6-ippool: '["ns-v6-ippool1", "ns-v6-ippool2", "wildcard-v6?"]'
   name: kube-system
 ...
 ```
@@ -131,8 +138,8 @@ metadata:
   "master": "eth0",
   "ipam": {
     "type": "spiderpool",
-    "default_ipv4_ippool":["default-v4-ippool","backup-ipv4-ippool"],
-    "default_ipv6_ippool":["default-v6-ippool","backup-ipv6-ippool"]
+    "default_ipv4_ippool":["default-v4-ippool", "backup-ipv4-ippool", "wildcard-v4-ippool[123]"],
+    "default_ipv6_ippool":["default-v6-ippool", "backup-ipv6-ippool", "wildcard-v6-ippool[456]"]
     }
 }
 ```
diff --git a/docs/usage/spider-ippool.md b/docs/usage/spider-ippool.md
index 881144b89..4bd5c64ae 100644
--- a/docs/usage/spider-ippool.md
+++ b/docs/usage/spider-ippool.md
@@ -55,7 +55,14 @@ spec:
 
 ### Specify IPPool to Allocate IP Addresses to Applications
 
-> For the priority rules when specifying the SpiderIPPool, refer to the [Candidate Pool Acquisition](../concepts/ipam-des.md#candidate-pool-acquisition).
+This feature owns 4 usage options including: `Use Pod Annotation to Specify IP Pool`, `Use Namespace Annotation to Specify IP Pool`, `Use CNI Configuration File to Specify IP Pool` and `Set Cluster Default Level for SpiderIPPool`.
+
+> For the priority rules when specifying the SpiderIPPool, refer to the [Candidate Pool Acquisition](../concepts/ipam-des.md#candidate-pool-acquisition).  
+> Additionally, with the following ways of specifying IPPools(Pod Annotation, Namespace Annotation, CNI configuration file) you can also use wildcards '*', '?' and '[]' to match the desired IPPools. For example: ipam.spidernet.io/ippool: '{"ipv4": ["demo-v4-ippool1", "backup-ipv4*"]}'
+>
+>   1. '*': Matches zero or more characters. For example, "ab" can match "ab", "abc", "abcd", and so on.
+>   2. '?': Matches a single character. For example, "a?c" can match "abc", "adc", "axc", and so on.
+>   3. '[]': Matches a specified range of characters. You can specify the choices of characters inside the brackets, or use a hyphen to specify a character range. For example, "[abc]" can match any one of the characters "a", "b", or "c".
 
 #### Use Pod Annotation to Specify IP Pool
 
@@ -64,8 +71,8 @@ You can use annotations like `ipam.spidernet.io/ippool` or `ipam.spidernet.io/ip
 ```yaml
 ipam.spidernet.io/ippool: |-
   {
-    "ipv4": ["demo-v4-ippool1", "backup-ipv4-ippool"],
-    "ipv6": ["demo-v6-ippool1", "backup-ipv6-ippool"]
+    "ipv4": ["demo-v4-ippool1", "backup-ipv4-ippool", "wildcard-v4?"],
+    "ipv6": ["demo-v6-ippool1", "backup-ipv6-ippool", "wildcard-v6*"]
   }
 ```
 
@@ -76,11 +83,11 @@ When using the annotation `ipam.spidernet.io/ippools` for specifying multiple ne
 ```yaml
 ipam.spidernet.io/ippools: |-
   [{
-      "ipv4": ["demo-v4-ippool1"],
-      "ipv6": ["demo-v6-ippool1"],
+      "ipv4": ["demo-v4-ippool1", "wildcard-v4-ippool[123]"],
+      "ipv6": ["demo-v6-ippool1", "wildcard-v6-ippool[123]"]
    },{
-      "ipv4": ["demo-v4-ippool2"],
-      "ipv6": ["demo-v6-ippool2"],
+      "ipv4": ["demo-v4-ippool2", "wildcard-v4-ippool[456]"],
+      "ipv6": ["demo-v6-ippool2", "wildcard-v6-ippool[456]"],
       "cleangateway": true
   }]
 ```
@@ -89,13 +96,13 @@ ipam.spidernet.io/ippools: |-
 ipam.spidernet.io/ippools: |-
   [{
       "interface": "eth0",
-      "ipv4": ["demo-v4-ippool1"],
-      "ipv6": ["demo-v6-ippool1"],
+      "ipv4": ["demo-v4-ippool1", "wildcard-v4-ippool[123]"],
+      "ipv6": ["demo-v6-ippool1", "wildcard-v6-ippool[123]"],
       "cleangateway": true
    },{
       "interface": "net1",
-      "ipv4": ["demo-v4-ippool2"],
-      "ipv6": ["demo-v6-ippool2"],
+      "ipv4": ["demo-v4-ippool2", "wildcard-v4-ippool[456]"],
+      "ipv6": ["demo-v6-ippool2", "wildcard-v6-ippool[456]"],
       "cleangateway": false
   }]
 ```
@@ -112,8 +119,8 @@ apiVersion: v1
 kind: Namespace
 metadata:
   annotations:
-    ipam.spidernet.io/default-ipv4-ippool: '["ns-v4-ippool1","ns-v4-ippool2"]'
-    ipam.spidernet.io/default-ipv6-ippool: '["ns-v6-ippool1","ns-v6-ippool2"]'
+    ipam.spidernet.io/default-ipv4-ippool: '["ns-v4-ippool1", "ns-v4-ippool2", "wildcard-v4*"]'
+    ipam.spidernet.io/default-ipv6-ippool: '["ns-v6-ippool1", "ns-v6-ippool2", "wildcard-v6?"]'
   name: kube-system
 ...
 ```
@@ -131,8 +138,8 @@ You can specify the default IPv4 and IPv6 pools for an application in the CNI co
   "master": "eth0",
   "ipam": {
     "type": "spiderpool",
-    "default_ipv4_ippool":["default-v4-ippool","backup-ipv4-ippool"],
-    "default_ipv6_ippool":["default-v6-ippool","backup-ipv6-ippool"]
+    "default_ipv4_ippool":["default-v4-ippool", "backup-ipv4-ippool", "wildcard-v4-ippool[123]"],
+    "default_ipv6_ippool":["default-v6-ippool", "backup-ipv6-ippool", "wildcard-v6-ippool[456]"]
     }
 }
 ```
diff --git a/docs/usage/spider-subnet-zh_CN.md b/docs/usage/spider-subnet-zh_CN.md
index 35e4d00dc..15d665472 100644
--- a/docs/usage/spider-subnet-zh_CN.md
+++ b/docs/usage/spider-subnet-zh_CN.md
@@ -166,7 +166,7 @@ subnet-7   4         10.7.0.0/16   0                    10
 
 以下的示例 Yaml 中， 会创建 2 个副本的 Deployment 应用 ，其中：
 
-- `ipam.spidernet.io/subnet`：用于指定 Spiderpool 的子网，Spiderpool 会自动在该子网中随机选择一些 IP 来创建固定 IP 池，与本应用绑定，实现 IP 固定的效果。在本示例中该注解会为 Pod 创建 1 个对应子网的固定 IP 池。
+- `ipam.spidernet.io/subnet`：用于指定 Spiderpool 的子网，Spiderpool 会自动在该子网中随机选择一些 IP 来创建固定 IP 池，与本应用绑定，实现 IP 固定的效果。在本示例中该注解会为 Pod 创建 1 个对应子网的固定 IP 池。(注意：不支持通配符的形式。)
 
 - `ipam.spidernet.io/ippool-ip-number`：用于指定创建 IP 池 中 的 IP 数量。该 annotation 的写法支持两种方式：一种是数字的方式指定 IP 池的固定数量，例如 `ipam.spidernet.io/ippool-ip-number：1`；另一种方式是使用加号和数字指定 IP 池的相对数量，例如`ipam.spidernet.io/ippool-ip-number：+1`，即表示 IP 池中的数量会自动实时保持在应用的副本数的基础上多 1 个 IP，以解决应用在弹性扩缩容的时有临时的 IP 可用。
 
diff --git a/docs/usage/spider-subnet.md b/docs/usage/spider-subnet.md
index c44b8207e..e2c1e763f 100644
--- a/docs/usage/spider-subnet.md
+++ b/docs/usage/spider-subnet.md
@@ -167,7 +167,7 @@ subnet-7   4         10.7.0.0/16   0                    10
 
 The following YAML example creates two replicas of a Deployment application:
 
-- `ipam.spidernet.io/subnet`: specifies the Spiderpool subnet. Spiderpool automatically selects IP addresses from this subnet to create a fixed IP pool associated with the application, ensuring fixed IP assignment.
+- `ipam.spidernet.io/subnet`: specifies the Spiderpool subnet. Spiderpool automatically selects IP addresses from this subnet to create a fixed IP pool associated with the application, ensuring fixed IP assignment. (Notice: this feature don't support wildcard.)
 
 - `ipam.spidernet.io/ippool-ip-number`: specifies the number of IP addresses in the IP pool. This annotation can be written in two ways: specifying a fixed quantity using a numeric value, such as `ipam.spidernet.io/ippool-ip-number：1`, or specifying a relative quantity using a plus and a number, such as `ipam.spidernet.io/ippool-ip-number：+1`. The latter means that the IP pool will dynamically maintain an additional IP address based on the number of replicas, ensuring temporary IPs are available during elastic scaling.
 
diff --git a/pkg/constant/k8s.go b/pkg/constant/k8s.go
index d9c605123..116e3a6f9 100644
--- a/pkg/constant/k8s.go
+++ b/pkg/constant/k8s.go
@@ -160,3 +160,14 @@ const (
 )
 
 const WebhookMutateRoute = "/webhook-health-check"
+
+// CRD field
+const (
+	SpecIPVersionField = "spec.ipVersion"
+	SpecDefaultField   = "spec.default"
+)
+
+const (
+	Str4 = "4"
+	Str6 = "6"
+)
diff --git a/pkg/ipam/pool_selections.go b/pkg/ipam/pool_selections.go
index 741b611df..e1d7f41bc 100644
--- a/pkg/ipam/pool_selections.go
+++ b/pkg/ipam/pool_selections.go
@@ -41,12 +41,12 @@ func (i *ipam) getPoolCandidates(ctx context.Context, addArgs *models.IpamAddArg
 
 	// Select IPPool candidates through the Pod annotation "ipam.spidernet.io/ippools".
 	if anno, ok := pod.Annotations[constant.AnnoPodIPPools]; ok {
-		return getPoolFromPodAnnoPools(ctx, anno, *addArgs.IfName)
+		return i.getPoolFromPodAnnoPools(ctx, anno, *addArgs.IfName)
 	}
 
 	// Select IPPool candidates through the Pod annotation "ipam.spidernet.io/ippool".
 	if anno, ok := pod.Annotations[constant.AnnoPodIPPool]; ok {
-		t, err := getPoolFromPodAnnoPool(ctx, anno, *addArgs.IfName, addArgs.CleanGateway)
+		t, err := i.getPoolFromPodAnnoPool(ctx, anno, *addArgs.IfName, addArgs.CleanGateway)
 		if err != nil {
 			return nil, err
 		}
@@ -64,7 +64,11 @@ func (i *ipam) getPoolCandidates(ctx context.Context, addArgs *models.IpamAddArg
 	}
 
 	// Select IPPool candidates through CNI network configuration.
-	if t := getPoolFromNetConf(ctx, *addArgs.IfName, addArgs.DefaultIPV4IPPool, addArgs.DefaultIPV6IPPool, addArgs.CleanGateway); t != nil {
+	t, err = i.getPoolFromNetConf(ctx, *addArgs.IfName, addArgs.DefaultIPV4IPPool, addArgs.DefaultIPV6IPPool, addArgs.CleanGateway)
+	if nil != err {
+		return nil, err
+	}
+	if t != nil {
 		return ToBeAllocateds{t}, nil
 	}
 
@@ -73,7 +77,6 @@ func (i *ipam) getPoolCandidates(ctx context.Context, addArgs *models.IpamAddArg
 	if err != nil {
 		return nil, err
 	}
-
 	return ToBeAllocateds{t}, nil
 }
 
@@ -301,7 +304,7 @@ func (i *ipam) applyThirdControllerAutoPool(ctx context.Context, subnetName stri
 	return pool, nil
 }
 
-func getPoolFromPodAnnoPools(ctx context.Context, anno, currentNIC string) (ToBeAllocateds, error) {
+func (i *ipam) getPoolFromPodAnnoPools(ctx context.Context, anno, currentNIC string) (ToBeAllocateds, error) {
 	logger := logutils.FromContext(ctx)
 	logger.Sugar().Infof("Use IPPools from Pod annotation '%s'", constant.AnnoPodIPPools)
 
@@ -312,6 +315,27 @@ func getPoolFromPodAnnoPools(ctx context.Context, anno, currentNIC string) (ToBe
 		return nil, fmt.Errorf("%w: %v", errPrefix, err)
 	}
 
+	for index := range annoPodIPPools {
+		if len(annoPodIPPools[index].IPv4Pools) != 0 {
+			newPoolNames, hasWildcard, err := i.ipPoolManager.ParseWildcardPoolNameList(ctx, annoPodIPPools[index].IPv4Pools, constant.IPv4)
+			if nil != err {
+				return nil, err
+			}
+			if hasWildcard {
+				annoPodIPPools[index].IPv4Pools = newPoolNames
+			}
+		}
+		if len(annoPodIPPools[index].IPv6Pools) != 0 {
+			newPoolNames, hasWildcard, err := i.ipPoolManager.ParseWildcardPoolNameList(ctx, annoPodIPPools[index].IPv6Pools, constant.IPv6)
+			if nil != err {
+				return nil, err
+			}
+			if hasWildcard {
+				annoPodIPPools[index].IPv6Pools = newPoolNames
+			}
+		}
+	}
+
 	// validate and mutate the IPPools annotation value
 	err = validateAndMutateMultipleNICAnnotations(annoPodIPPools, currentNIC)
 	if nil != err {
@@ -342,7 +366,7 @@ func getPoolFromPodAnnoPools(ctx context.Context, anno, currentNIC string) (ToBe
 	return tt, nil
 }
 
-func getPoolFromPodAnnoPool(ctx context.Context, anno, nic string, cleanGateway bool) (*ToBeAllocated, error) {
+func (i *ipam) getPoolFromPodAnnoPool(ctx context.Context, anno, nic string, cleanGateway bool) (*ToBeAllocated, error) {
 	logger := logutils.FromContext(ctx)
 	logger.Sugar().Infof("Use IPPools from Pod annotation '%s'", constant.AnnoPodIPPool)
 
@@ -352,6 +376,29 @@ func getPoolFromPodAnnoPool(ctx context.Context, anno, nic string, cleanGateway
 		return nil, fmt.Errorf("%w: %v", errPrefix, err)
 	}
 
+	// check IPv4 PoolName wildcard
+	if len(annoPodIPPool.IPv4Pools) != 0 {
+		newPoolNames, hasWildcard, err := i.ipPoolManager.ParseWildcardPoolNameList(ctx, annoPodIPPool.IPv4Pools, constant.IPv4)
+		if nil != err {
+			return nil, err
+		}
+		// overwrite the annoPodIPPool
+		if hasWildcard {
+			annoPodIPPool.IPv4Pools = newPoolNames
+		}
+	}
+	// check IPv6 PoolName wildcard
+	if len(annoPodIPPool.IPv6Pools) != 0 {
+		newPoolNames, hasWildcard, err := i.ipPoolManager.ParseWildcardPoolNameList(ctx, annoPodIPPool.IPv6Pools, constant.IPv6)
+		if nil != err {
+			return nil, err
+		}
+		// overwrite the annoPodIPPool
+		if hasWildcard {
+			annoPodIPPool.IPv6Pools = newPoolNames
+		}
+	}
+
 	t := &ToBeAllocated{
 		NIC:          nic,
 		CleanGateway: cleanGateway,
@@ -386,6 +433,25 @@ func (i *ipam) getPoolFromNS(ctx context.Context, namespace, nic string, cleanGa
 		return nil, nil
 	}
 
+	if len(nsDefaultV4Pools) != 0 {
+		newPoolNames, hasWildcard, err := i.ipPoolManager.ParseWildcardPoolNameList(ctx, nsDefaultV4Pools, constant.IPv4)
+		if nil != err {
+			return nil, err
+		}
+		if hasWildcard {
+			nsDefaultV4Pools = newPoolNames
+		}
+	}
+	if len(nsDefaultV6Pools) != 0 {
+		newPoolNames, hasWildcard, err := i.ipPoolManager.ParseWildcardPoolNameList(ctx, nsDefaultV6Pools, constant.IPv6)
+		if nil != err {
+			return nil, err
+		}
+		if hasWildcard {
+			nsDefaultV6Pools = newPoolNames
+		}
+	}
+
 	logger := logutils.FromContext(ctx)
 	logger.Sugar().Infof("Use IPPools from Namespace annotation '%s'", constant.AnnotationPre+"/default-ipv(4/6)-ippool")
 
@@ -409,9 +475,27 @@ func (i *ipam) getPoolFromNS(ctx context.Context, namespace, nic string, cleanGa
 	return t, nil
 }
 
-func getPoolFromNetConf(ctx context.Context, nic string, netConfV4Pool, netConfV6Pool []string, cleanGateway bool) *ToBeAllocated {
+func (i *ipam) getPoolFromNetConf(ctx context.Context, nic string, netConfV4Pool, netConfV6Pool []string, cleanGateway bool) (*ToBeAllocated, error) {
 	if len(netConfV4Pool) == 0 && len(netConfV6Pool) == 0 {
-		return nil
+		return nil, nil
+	}
+	if len(netConfV4Pool) != 0 {
+		newPoolNames, hasWildcard, err := i.ipPoolManager.ParseWildcardPoolNameList(ctx, netConfV4Pool, constant.IPv4)
+		if nil != err {
+			return nil, err
+		}
+		if hasWildcard {
+			netConfV4Pool = newPoolNames
+		}
+	}
+	if len(netConfV6Pool) != 0 {
+		newPoolNames, hasWildcard, err := i.ipPoolManager.ParseWildcardPoolNameList(ctx, netConfV6Pool, constant.IPv6)
+		if nil != err {
+			return nil, err
+		}
+		if hasWildcard {
+			netConfV6Pool = newPoolNames
+		}
 	}
 
 	logger := logutils.FromContext(ctx)
@@ -434,14 +518,14 @@ func getPoolFromNetConf(ctx context.Context, nic string, netConfV4Pool, netConfV
 		})
 	}
 
-	return t
+	return t, nil
 }
 
 func (i *ipam) getClusterDefaultPools(ctx context.Context, nic string, cleanGateway bool) (*ToBeAllocated, error) {
 	ipPoolList, err := i.ipPoolManager.ListIPPools(
 		ctx,
 		constant.UseCache,
-		client.MatchingFields{"spec.default": strconv.FormatBool(true)},
+		client.MatchingFields{constant.SpecDefaultField: strconv.FormatBool(true)},
 	)
 	if err != nil {
 		return nil, err
diff --git a/pkg/ipam/utils.go b/pkg/ipam/utils.go
index 1049d632b..c6f6b7b93 100644
--- a/pkg/ipam/utils.go
+++ b/pkg/ipam/utils.go
@@ -7,13 +7,12 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
-	"net"
-	"strconv"
-
 	appsv1 "k8s.io/api/apps/v1"
 	batchv1 "k8s.io/api/batch/v1"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/utils/strings/slices"
+	"net"
+	"strconv"
 
 	"github.com/spidernet-io/spiderpool/api/v1/agent/models"
 	subnetmanagercontrollers "github.com/spidernet-io/spiderpool/pkg/applicationcontroller/applicationinformers"
diff --git a/pkg/ippoolmanager/ippool_manager.go b/pkg/ippoolmanager/ippool_manager.go
index 4aa7609bb..53ebb2a04 100644
--- a/pkg/ippoolmanager/ippool_manager.go
+++ b/pkg/ippoolmanager/ippool_manager.go
@@ -7,6 +7,7 @@ import (
 	"context"
 	"fmt"
 	"net"
+	"path/filepath"
 
 	"go.uber.org/zap"
 	corev1 "k8s.io/api/core/v1"
@@ -35,6 +36,7 @@ type IPPoolManager interface {
 	AllocateIP(ctx context.Context, poolName, nic string, pod *corev1.Pod, podController types.PodTopController) (*models.IPConfig, error)
 	ReleaseIP(ctx context.Context, poolName string, ipAndUIDs []types.IPAndUID) error
 	UpdateAllocatedIPs(ctx context.Context, poolName, namespacedName string, ipAndCIDs []types.IPAndUID) error
+	ParseWildcardPoolNameList(ctx context.Context, PoolNames []string, ipVersion types.IPVersion) (newPoolNames []string, hasWildcard bool, err error)
 }
 
 type ipPoolManager struct {
@@ -356,3 +358,42 @@ func (im *ipPoolManager) UpdateAllocatedIPs(ctx context.Context, poolName, names
 
 	return nil
 }
+
+func (im *ipPoolManager) ParseWildcardPoolNameList(ctx context.Context, poolNamesArr []string, ipVersion types.IPVersion) (newPoolNames []string, hasWildcard bool, err error) {
+	if HasWildcardInSlice(poolNamesArr) {
+		var ipVersionStr string
+		if ipVersion == constant.IPv4 {
+			ipVersionStr = constant.Str4
+		} else {
+			ipVersionStr = constant.Str6
+		}
+
+		poolList, err := im.ListIPPools(ctx, constant.UseCache, client.MatchingFields{constant.SpecIPVersionField: ipVersionStr})
+		if nil != err {
+			return nil, false, err
+		}
+
+		newPoolNamesArr := []string{}
+		for _, tmpStr := range poolNamesArr {
+			if HasWildcardInStr(tmpStr) {
+				for _, tmpPool := range poolList.Items {
+					isMatch, err := filepath.Match(tmpStr, tmpPool.Name)
+					if nil != err {
+						return nil, false, fmt.Errorf("failed to match wildcard: IPv%d PoolName pattern '%s', character '%s', error: %v", ipVersion, tmpStr, tmpPool.Name, err)
+					}
+					// wildcard matches
+					if isMatch {
+						newPoolNamesArr = append(newPoolNamesArr, tmpPool.Name)
+					}
+				}
+			} else {
+				// original IPPool name
+				newPoolNamesArr = append(newPoolNamesArr, tmpStr)
+			}
+		}
+
+		return newPoolNamesArr, true, nil
+	}
+
+	return poolNamesArr, false, nil
+}
diff --git a/pkg/ippoolmanager/ippool_validate.go b/pkg/ippoolmanager/ippool_validate.go
index b8288e6dc..5d281f797 100644
--- a/pkg/ippoolmanager/ippool_validate.go
+++ b/pkg/ippoolmanager/ippool_validate.go
@@ -8,7 +8,6 @@ import (
 	"fmt"
 	"strconv"
 
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/validation"
 	"k8s.io/apimachinery/pkg/util/validation/field"
 	"k8s.io/utils/strings/slices"
@@ -200,10 +199,8 @@ func (iw *IPPoolWebhook) validateIPPoolCIDR(ctx context.Context, ipPool *spiderp
 
 	for _, pool := range ipPoolList.Items {
 		if *pool.Spec.IPVersion == *ipPool.Spec.IPVersion {
-			// since we met already exist IPPool resource, we just return the error to avoid the following taxing operations.
-			// the user can also use k8s 'errors.IsAlreadyExists' to get the right error type assertion.
 			if pool.Name == ipPool.Name {
-				return field.InternalError(subnetField, fmt.Errorf("IPPool %s %s", ipPool.Name, metav1.StatusReasonAlreadyExists))
+				return field.InternalError(subnetField, fmt.Errorf("IPPool %s already exists", ipPool.Name))
 			}
 
 			if pool.Spec.Subnet == ipPool.Spec.Subnet {
diff --git a/pkg/ippoolmanager/ippool_webhook.go b/pkg/ippoolmanager/ippool_webhook.go
index 28ea7a2f7..ef962b06f 100644
--- a/pkg/ippoolmanager/ippool_webhook.go
+++ b/pkg/ippoolmanager/ippool_webhook.go
@@ -6,11 +6,9 @@ package ippoolmanager
 import (
 	"context"
 	"errors"
-	"strings"
 
 	"go.uber.org/zap"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	ctrl "sigs.k8s.io/controller-runtime"
@@ -79,20 +77,12 @@ func (iw *IPPoolWebhook) ValidateCreate(ctx context.Context, obj runtime.Object)
 	logger.Sugar().Debugf("Request IPPool: %+v", *ipPool)
 
 	if errs := iw.validateCreateIPPoolWhileEnableSpiderSubnet(logutils.IntoContext(ctx, logger), ipPool); len(errs) != 0 {
-		aggregatedErr := errs.ToAggregate()
-		logger.Sugar().Errorf("Failed to create IPPool: %s", aggregatedErr)
-		// the user will receive the following errors rather than K8S API server specific typed errors.
-		// Refer to https://github.com/spidernet-io/spiderpool/issues/3321
-		switch {
-		case strings.Contains(aggregatedErr.Error(), string(metav1.StatusReasonAlreadyExists)):
-			return nil, apierrors.NewAlreadyExists(spiderpoolv2beta1.Resource(constant.KindSpiderIPPool), ipPool.Name)
-		default:
-			return nil, apierrors.NewInvalid(
-				schema.GroupKind{Group: constant.SpiderpoolAPIGroup, Kind: constant.KindSpiderIPPool},
-				ipPool.Name,
-				errs,
-			)
-		}
+		logger.Sugar().Errorf("Failed to create IPPool: %v", errs.ToAggregate().Error())
+		return nil, apierrors.NewInvalid(
+			schema.GroupKind{Group: constant.SpiderpoolAPIGroup, Kind: constant.KindSpiderIPPool},
+			ipPool.Name,
+			errs,
+		)
 	}
 
 	return nil, nil
diff --git a/pkg/ippoolmanager/utils.go b/pkg/ippoolmanager/utils.go
index f0fbf1127..28e118673 100644
--- a/pkg/ippoolmanager/utils.go
+++ b/pkg/ippoolmanager/utils.go
@@ -136,3 +136,26 @@ func findAllocatedIPFromRecords(allocatedRecords spiderpoolv2beta1.PoolIPAllocat
 
 	return "", false
 }
+
+// HasWildcardInStr checks whether the wildcard '*', '?', '[]' exists in the given string variable
+func HasWildcardInStr(str string) bool {
+	switch {
+	case strings.Contains(str, "?"):
+		return true
+	case strings.Contains(str, "*"):
+		return true
+	case strings.Contains(str, "[") && strings.Contains(str, "]"):
+		return true
+	}
+
+	return false
+}
+
+func HasWildcardInSlice(arr []string) bool {
+	for _, str := range arr {
+		if HasWildcardInStr(str) {
+			return true
+		}
+	}
+	return false
+}
diff --git a/pkg/reservedipmanager/reservedip_manager.go b/pkg/reservedipmanager/reservedip_manager.go
index 34caafd42..54c57251f 100644
--- a/pkg/reservedipmanager/reservedip_manager.go
+++ b/pkg/reservedipmanager/reservedip_manager.go
@@ -76,7 +76,7 @@ func (rm *reservedIPManager) AssembleReservedIPs(ctx context.Context, version ty
 		return nil, err
 	}
 
-	rIPList, err := rm.ListReservedIPs(ctx, constant.UseCache, client.MatchingFields{"spec.ipVersion": strconv.FormatInt(version, 10)})
+	rIPList, err := rm.ListReservedIPs(ctx, constant.UseCache, client.MatchingFields{constant.SpecIPVersionField: strconv.FormatInt(version, 10)})
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/subnetmanager/subnet_validate.go b/pkg/subnetmanager/subnet_validate.go
index 70ca92f38..f4dbf8e19 100644
--- a/pkg/subnetmanager/subnet_validate.go
+++ b/pkg/subnetmanager/subnet_validate.go
@@ -8,7 +8,6 @@ import (
 	"fmt"
 	"strconv"
 
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/util/validation/field"
 
 	"github.com/spidernet-io/spiderpool/pkg/constant"
@@ -196,10 +195,8 @@ func (sw *SubnetWebhook) validateSubnetCIDR(ctx context.Context, subnet *spiderp
 
 	for _, s := range subnetList.Items {
 		if *s.Spec.IPVersion == *subnet.Spec.IPVersion {
-			// since we met already exist Subnet resource, we just return the error to avoid the following taxing operations.
-			// the user can also use k8s 'errors.IsAlreadyExists' to get the right error type assertion.
 			if s.Name == subnet.Name {
-				return field.InternalError(subnetField, fmt.Errorf("subnet %s %s", subnet.Name, metav1.StatusReasonAlreadyExists))
+				return field.InternalError(subnetField, fmt.Errorf("subnet %s already exists", subnet.Name))
 			}
 
 			overlap, err := spiderpoolip.IsCIDROverlap(*subnet.Spec.IPVersion, subnet.Spec.Subnet, s.Spec.Subnet)
diff --git a/pkg/subnetmanager/subnet_webhook.go b/pkg/subnetmanager/subnet_webhook.go
index b7dafcb42..6870e6d39 100644
--- a/pkg/subnetmanager/subnet_webhook.go
+++ b/pkg/subnetmanager/subnet_webhook.go
@@ -6,11 +6,9 @@ package subnetmanager
 import (
 	"context"
 	"errors"
-	"strings"
 
 	"go.uber.org/zap"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	ctrl "sigs.k8s.io/controller-runtime"
@@ -78,20 +76,12 @@ func (sw *SubnetWebhook) ValidateCreate(ctx context.Context, obj runtime.Object)
 	logger.Sugar().Debugf("Request Subnet: %+v", *subnet)
 
 	if errs := sw.validateCreateSubnet(logutils.IntoContext(ctx, logger), subnet); len(errs) != 0 {
-		aggregatedErr := errs.ToAggregate()
-		logger.Sugar().Errorf("Failed to create Subnet: %s", aggregatedErr)
-		// the user will receive the following errors rather than K8S API server specific typed errors.
-		// Refer to https://github.com/spidernet-io/spiderpool/issues/3321
-		switch {
-		case strings.Contains(aggregatedErr.Error(), string(metav1.StatusReasonAlreadyExists)):
-			return nil, apierrors.NewAlreadyExists(spiderpoolv2beta1.Resource(constant.KindSpiderSubnet), subnet.Name)
-		default:
-			return nil, apierrors.NewInvalid(
-				schema.GroupKind{Group: constant.SpiderpoolAPIGroup, Kind: constant.KindSpiderSubnet},
-				subnet.Name,
-				errs,
-			)
-		}
+		logger.Sugar().Errorf("Failed to create Subnet: %v", errs.ToAggregate().Error())
+		return nil, apierrors.NewInvalid(
+			schema.GroupKind{Group: constant.SpiderpoolAPIGroup, Kind: constant.KindSpiderSubnet},
+			subnet.Name,
+			errs,
+		)
 	}
 
 	return nil, nil


Test patch:
diff --git a/pkg/ippoolmanager/ippool_manager_suite_test.go b/pkg/ippoolmanager/ippool_manager_suite_test.go
index aa4b96b8e..655286f51 100644
--- a/pkg/ippoolmanager/ippool_manager_suite_test.go
+++ b/pkg/ippoolmanager/ippool_manager_suite_test.go
@@ -61,10 +61,17 @@ var _ = BeforeSuite(func() {
 			ipPool := raw.(*spiderpoolv2beta1.SpiderIPPool)
 			return []string{ipPool.GetObjectMeta().GetName()}
 		}).
-		WithIndex(&spiderpoolv2beta1.SpiderIPPool{}, "spec.default", func(raw client.Object) []string {
+		WithIndex(&spiderpoolv2beta1.SpiderIPPool{}, constant.SpecDefaultField, func(raw client.Object) []string {
 			ipPool := raw.(*spiderpoolv2beta1.SpiderIPPool)
 			return []string{strconv.FormatBool(*ipPool.Spec.Default)}
 		}).
+		WithIndex(&spiderpoolv2beta1.SpiderIPPool{}, constant.SpecIPVersionField, func(raw client.Object) []string {
+			ipPool := raw.(*spiderpoolv2beta1.SpiderIPPool)
+			if ipPool.Spec.IPVersion != nil {
+				return []string{strconv.FormatInt(*ipPool.Spec.IPVersion, 10)}
+			}
+			return []string{}
+		}).
 		WithStatusSubresource(&spiderpoolv2beta1.SpiderIPPool{}).
 		Build()
 
@@ -76,7 +83,7 @@ var _ = BeforeSuite(func() {
 			ipPool := raw.(*spiderpoolv2beta1.SpiderIPPool)
 			return []string{ipPool.GetObjectMeta().GetName()}
 		}).
-		WithIndex(&spiderpoolv2beta1.SpiderIPPool{}, "spec.default", func(raw client.Object) []string {
+		WithIndex(&spiderpoolv2beta1.SpiderIPPool{}, constant.SpecDefaultField, func(raw client.Object) []string {
 			ipPool := raw.(*spiderpoolv2beta1.SpiderIPPool)
 			return []string{strconv.FormatBool(*ipPool.Spec.Default)}
 		}).
diff --git a/pkg/ippoolmanager/ippool_manager_test.go b/pkg/ippoolmanager/ippool_manager_test.go
index f80b31b88..f1fa2ca71 100644
--- a/pkg/ippoolmanager/ippool_manager_test.go
+++ b/pkg/ippoolmanager/ippool_manager_test.go
@@ -659,5 +659,61 @@ var _ = Describe("IPPoolManager", Label("ippool_manager_test"), func() {
 				Expect(newRecords[ip].PodUID).To(Equal(newUID))
 			})
 		})
+
+		Describe("ParseWildcardPoolNameList", func() {
+			It("standard IPPool names", func() {
+				poolNamesArr := []string{"pool1", "pool2"}
+				newPoolNames, hasWildcard, err := ipPoolManager.ParseWildcardPoolNameList(ctx, poolNamesArr, constant.IPv4)
+				Expect(err).NotTo(HaveOccurred())
+				Expect(hasWildcard).To(BeFalse())
+				Expect(newPoolNames).To(Equal(poolNamesArr))
+			})
+
+			It("wildcard IPPool name", func() {
+				ipPoolT.Spec.IPVersion = ptr.To(constant.IPv4)
+				err := fakeClient.Create(ctx, ipPoolT)
+				Expect(err).NotTo(HaveOccurred())
+
+				poolNamesArr := []string{"ippool*", "pp1"}
+				newPoolNames, hasWildcard, err := ipPoolManager.ParseWildcardPoolNameList(ctx, poolNamesArr, constant.IPv4)
+				Expect(err).NotTo(HaveOccurred())
+				Expect(hasWildcard).To(BeTrue())
+				Expect(newPoolNames).To(HaveLen(2))
+				Expect(newPoolNames[0]).To(Equal(ipPoolName))
+			})
+
+			It("wildcard IPPool name with no matched", func() {
+				ipPoolT.Spec.IPVersion = ptr.To(constant.IPv4)
+				err := fakeClient.Create(ctx, ipPoolT)
+				Expect(err).NotTo(HaveOccurred())
+
+				// this wildcard would not match any IPPools' name
+				poolNamesArr := []string{"aaa*"}
+				newPoolNames, hasWildcard, err := ipPoolManager.ParseWildcardPoolNameList(ctx, poolNamesArr, constant.IPv4)
+				Expect(err).NotTo(HaveOccurred())
+				Expect(hasWildcard).To(BeTrue())
+				Expect(newPoolNames).To(HaveLen(0))
+			})
+
+			It("invalid wildcard", func() {
+				ipPoolT.Spec.IPVersion = ptr.To(constant.IPv6)
+				err := fakeClient.Create(ctx, ipPoolT)
+				Expect(err).NotTo(HaveOccurred())
+
+				poolNamesArr := []string{"p1", "pool*", "[ippool]["}
+				_, _, err = ipPoolManager.ParseWildcardPoolNameList(ctx, poolNamesArr, constant.IPv6)
+				Expect(err).To(HaveOccurred())
+			})
+
+			It("failed to call ListIPPools with wildcard usage", func() {
+				patches := gomonkey.ApplyMethodReturn(fakeClient, "List", constant.ErrUnknown)
+				defer patches.Reset()
+
+				poolNamesArr := []string{"ippool*", "pp1"}
+				_, _, err := ipPoolManager.ParseWildcardPoolNameList(ctx, poolNamesArr, constant.IPv4)
+				Expect(err).To(HaveOccurred())
+				Expect(err).To(MatchError(constant.ErrUnknown))
+			})
+		})
 	})
 })
diff --git a/pkg/ippoolmanager/ippool_webhook_test.go b/pkg/ippoolmanager/ippool_webhook_test.go
index 07766f5ed..e356e43ce 100644
--- a/pkg/ippoolmanager/ippool_webhook_test.go
+++ b/pkg/ippoolmanager/ippool_webhook_test.go
@@ -680,7 +680,7 @@ var _ = Describe("IPPoolWebhook", Label("ippool_webhook_test"), func() {
 					Expect(err).NotTo(HaveOccurred())
 
 					warns, err := ipPoolWebhook.ValidateCreate(ctx, ipPoolT)
-					Expect(apierrors.IsAlreadyExists(err)).To(BeTrue())
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
 					Expect(warns).To(BeNil())
 				})
 
diff --git a/pkg/ippoolmanager/utils_test.go b/pkg/ippoolmanager/utils_test.go
index 909049de0..b5e374d46 100644
--- a/pkg/ippoolmanager/utils_test.go
+++ b/pkg/ippoolmanager/utils_test.go
@@ -328,4 +328,28 @@ var _ = Describe("IPPoolManager-utils", Label("ippool_manager_utils"), func() {
 			Expect(hasFound).To(BeFalse())
 		})
 	})
+
+	Context("Test Wildcard", func() {
+		It("For single string variable", func() {
+			hasWildcardInStr := HasWildcardInStr("pool")
+			Expect(hasWildcardInStr).To(BeFalse())
+
+			hasWildcardInStr = HasWildcardInStr("pool?")
+			Expect(hasWildcardInStr).To(BeTrue())
+
+			hasWildcardInStr = HasWildcardInStr("pool*")
+			Expect(hasWildcardInStr).To(BeTrue())
+
+			hasWildcardInStr = HasWildcardInStr("pool[abc]")
+			Expect(hasWildcardInStr).To(BeTrue())
+		})
+
+		It("For string slice", func() {
+			hasWildcardInSlice := HasWildcardInSlice([]string{"pool1", "pool2", "pool3"})
+			Expect(hasWildcardInSlice).To(BeFalse())
+
+			hasWildcardInSlice = HasWildcardInSlice([]string{"pool1", "v4pool*", "pool2"})
+			Expect(hasWildcardInSlice).To(BeTrue())
+		})
+	})
 })
diff --git a/pkg/reservedipmanager/reservedip_manager_suite_test.go b/pkg/reservedipmanager/reservedip_manager_suite_test.go
index 82c174edc..743a2333a 100644
--- a/pkg/reservedipmanager/reservedip_manager_suite_test.go
+++ b/pkg/reservedipmanager/reservedip_manager_suite_test.go
@@ -11,12 +11,12 @@ import (
 	. "github.com/onsi/gomega"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
-
 	k8sscheme "k8s.io/client-go/kubernetes/scheme"
 	k8stesting "k8s.io/client-go/testing"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/client/fake"
 
+	"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/reservedipmanager"
 )
@@ -44,7 +44,7 @@ var _ = BeforeSuite(func() {
 			rIP := raw.(*spiderpoolv2beta1.SpiderReservedIP)
 			return []string{rIP.GetObjectMeta().GetName()}
 		}).
-		WithIndex(&spiderpoolv2beta1.SpiderReservedIP{}, "spec.ipVersion", func(raw client.Object) []string {
+		WithIndex(&spiderpoolv2beta1.SpiderReservedIP{}, constant.SpecIPVersionField, func(raw client.Object) []string {
 			rIP := raw.(*spiderpoolv2beta1.SpiderReservedIP)
 			return []string{strconv.FormatInt(*rIP.Spec.IPVersion, 10)}
 		}).
@@ -58,7 +58,7 @@ var _ = BeforeSuite(func() {
 			rIP := raw.(*spiderpoolv2beta1.SpiderReservedIP)
 			return []string{rIP.GetObjectMeta().GetName()}
 		}).
-		WithIndex(&spiderpoolv2beta1.SpiderReservedIP{}, "spec.ipVersion", func(raw client.Object) []string {
+		WithIndex(&spiderpoolv2beta1.SpiderReservedIP{}, constant.SpecIPVersionField, func(raw client.Object) []string {
 			rIP := raw.(*spiderpoolv2beta1.SpiderReservedIP)
 			return []string{strconv.FormatInt(*rIP.Spec.IPVersion, 10)}
 		}).
diff --git a/pkg/subnetmanager/subnet_webhook_test.go b/pkg/subnetmanager/subnet_webhook_test.go
index 772d0553f..5a9da1517 100644
--- a/pkg/subnetmanager/subnet_webhook_test.go
+++ b/pkg/subnetmanager/subnet_webhook_test.go
@@ -478,7 +478,7 @@ var _ = Describe("SubnetWebhook", Label("subnet_webhook_test"), func() {
 					Expect(err).NotTo(HaveOccurred())
 
 					warns, err := subnetWebhook.ValidateCreate(ctx, subnetT)
-					Expect(apierrors.IsAlreadyExists(err)).To(BeTrue())
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
 					Expect(warns).To(BeNil())
 				})
 
diff --git a/test/doc/annotation.md b/test/doc/annotation.md
index 3992cd3c5..184411e52 100644
--- a/test/doc/annotation.md
+++ b/test/doc/annotation.md
@@ -8,6 +8,7 @@
 | A00004  | Take a test with the Priority: pod annotation > namespace annotation > specified in a CNI profile                           | p1       |       | done   |       |
 | A00005  | The "IPPools" annotation has the higher Priority over the "IPPool" annotation                                               | p1       |       | done   |       |
 | A00006  | The namespace annotation has precedence over global default IPPool                                                          | p1       | true  | done   |       |
+| A00007  | Use wildcard for namespace annotation to specify IPPools                                                                    | p1       | true  | done   |       |
 | A00008  | Successfully run an annotated multi-container pod                                                                           | p2       |       | done   |       |
 | A00009  | Modify the annotated IPPool for a specified Deployment pod<br />Modify the annotated IPPool for a specified StatefulSet pod | p2       |       | done   |       |
 | A00010  | Modify the annotated IPPool for a pod running on multiple NICs                                                              | p3       |       | done   |       |
@@ -15,3 +16,4 @@
 | A00012  | Specify the default route NIC through Pod annotation: `ipam.spidernet.io/default-route-nic`                                 | p2       |       | done   |       |
 | A00013  | It's invalid to specify one NIC corresponding IPPool in IPPools annotation with multiple NICs                               | p2       |       | done   |       |
 | A00014  | It's invalid to specify same NIC name for IPPools annotation with multiple NICs                                             | p2       |       | done   |       |
+| A00015  | Use wildcard for 'ipam.spidernet.io/ippools' annotation to specify IPPools                                                  | p2       |       | done   |       |
diff --git a/test/e2e/annotation/annotation_test.go b/test/e2e/annotation/annotation_test.go
index a850c9236..523f01db5 100644
--- a/test/e2e/annotation/annotation_test.go
+++ b/test/e2e/annotation/annotation_test.go
@@ -346,11 +346,19 @@ var _ = Describe("test annotation", Label("annotation"), func() {
 			})
 		})
 
-		It(`the "ippools" annotation has the higher priority over the "ippool" annotation`, Label("A00005"), func() {
+		It(`the "ippools" annotation has the higher priority over the "ippool" annotation, and we'll use wildcard to specify the annotation`, Label("A00005", "A00015"), func() {
 			// Generate IPPool annotation string
 			podIppoolAnnoStr = common.GeneratePodIPPoolAnnotations(frame, common.NIC1, globalDefaultV4IpoolList, globalDefaultV6IpoolList)
-			// Generate IPPools annotation string
-			podIppoolsAnnoStr = common.GeneratePodIPPoolsAnnotations(frame, common.NIC1, cleanGateway, v4PoolNameList, v6PoolNameList)
+			var tmpV4PoolNameList, tmpV6PoolNameList []string
+			if frame.Info.IpV4Enabled {
+				tmpV4PoolNameList = []string{fmt.Sprintf("%s*", v4PoolNameList[0])}
+			}
+			if frame.Info.IpV6Enabled {
+				tmpV6PoolNameList = []string{fmt.Sprintf("%s*", v6PoolNameList[0])}
+			}
+			podIppoolsAnnoStr = common.GeneratePodIPPoolsAnnotations(frame, common.NIC1, cleanGateway, tmpV4PoolNameList, tmpV6PoolNameList)
+			GinkgoWriter.Printf("Annotation '%s' value is '%s'\n", pkgconstant.AnnoPodIPPools, podIppoolsAnnoStr)
+			GinkgoWriter.Printf("Annotation '%s' value is '%s'\n", pkgconstant.AnnoPodIPPool, podIppoolAnnoStr)
 
 			// Generate Pod Yaml with IPPool annotations and IPPools annotations
 			podYaml := common.GenerateExamplePodYaml(podName, nsName)
@@ -359,7 +367,7 @@ var _ = Describe("test annotation", Label("annotation"), func() {
 				pkgconstant.AnnoPodIPPools: podIppoolsAnnoStr,
 			}
 			Expect(podYaml).NotTo(BeNil())
-			GinkgoWriter.Printf("Successful to generate Pod Yaml with IPPool annotations and IPPools annotations")
+			GinkgoWriter.Println("Successful to generate Pod Yaml with IPPool annotations and IPPools annotations")
 
 			// The "ippools" annotation has a higher priority than the "ippool" annotation.
 			checkAnnotationPriority(podYaml, podName, nsName, v4PoolNameList, v6PoolNameList)
@@ -416,11 +424,11 @@ var _ = Describe("test annotation", Label("annotation"), func() {
 				namespaceObject.Annotations = make(map[string]string)
 				if frame.Info.IpV4Enabled {
 					v4IppoolAnnoValue := types.AnnoNSDefautlV4PoolValue{}
-					common.SetNamespaceIppoolAnnotation(v4IppoolAnnoValue, namespaceObject, v4PoolNameList, pkgconstant.AnnoNSDefautlV4Pool)
+					common.SetNamespaceIppoolAnnotation(v4IppoolAnnoValue, namespaceObject, []string{fmt.Sprintf("%s*", v4PoolName)}, pkgconstant.AnnoNSDefautlV4Pool)
 				}
 				if frame.Info.IpV6Enabled {
 					v6IppoolAnnoValue := types.AnnoNSDefautlV6PoolValue{}
-					common.SetNamespaceIppoolAnnotation(v6IppoolAnnoValue, namespaceObject, v6PoolNameList, pkgconstant.AnnoNSDefautlV6Pool)
+					common.SetNamespaceIppoolAnnotation(v6IppoolAnnoValue, namespaceObject, []string{fmt.Sprintf("%s*", v6PoolName)}, pkgconstant.AnnoNSDefautlV6Pool)
 				}
 				GinkgoWriter.Printf("Generate namespace objects: %v with namespace annotations \n", namespaceObject)
 
@@ -481,7 +489,7 @@ var _ = Describe("test annotation", Label("annotation"), func() {
 				checkAnnotationPriority(podYaml, podName, nsName, newV4PoolNameList, newV6PoolNameList)
 			})
 
-			It(`the namespace annotation has precedence over global default ippool`, Label("A00006", "smoke"), func() {
+			It(`the namespace annotation has precedence over global default ippool, and use wildcard for namespace annotation to specify IPPools`, Label("A00006", "A00007", "smoke"), func() {
 				// Generate a pod yaml with namespace annotations
 				podYaml := common.GenerateExamplePodYaml(podName, nsName)
 				Expect(podYaml).NotTo(BeNil())