Repository: spidernet-io/spiderpool
Pull request #1251 (2023-01-03 10:30:51)

test: Add webhook unit tests for pkg subnetmanager
Signed-off-by: iiiceoo <ziqian.xue@daocloud.io>


sonarcloud[bot]: SonarCloud Quality Gate failed.&nbsp; &nbsp; [![Quality Gate failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/failed-16px.png 'Quality Gate failed')](https://sonarcloud.io/dashboard?id=spidernet-io_spiderpool&pullRequest=1251)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=SECURITY_HOTSPOT) [![E](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/E-16px.png 'E')](https://sonarcloud.io/project/security_hotspots?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=SECURITY_HOTSPOT) [30 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=CODE_SMELL) [12 Code Smells](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=spidernet-io_spiderpool&pullRequest=1251) No Coverage information  
[![4.7%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/5-16px.png '4.7%')](https://sonarcloud.io/component_measures?id=spidernet-io_spiderpool&pullRequest=1251&metric=new_duplicated_lines_density&view=list) [4.7% Duplication](https://sonarcloud.io/component_measures?id=spidernet-io_spiderpool&pullRequest=1251&metric=new_duplicated_lines_density&view=list)


codecov[bot]: # [Codecov](https://codecov.io/gh/spidernet-io/spiderpool/pull/1251?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spidernet-io) Report
> Merging [#1251](https://codecov.io/gh/spidernet-io/spiderpool/pull/1251?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spidernet-io) (b349518) into [main](https://codecov.io/gh/spidernet-io/spiderpool/commit/52abb89d6fbf8cb6d7f2076554b2d1ef9165091c?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spidernet-io) (52abb89) will **decrease** coverage by `35.32%`.
> The diff coverage is `81.11%`.

[![Impacted file tree graph](https://codecov.io/gh/spidernet-io/spiderpool/pull/1251/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://codecov.io/gh/spidernet-io/spiderpool/pull/1251?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    #1251       +/-   ##
===========================================
- Coverage   87.88%   52.55%   -35.33%     
===========================================
  Files          32       38        +6     
  Lines        1873     3425     +1552     
===========================================
+ Hits         1646     1800      +154     
- Misses        200     1596     +1396     
- Partials       27       29        +2     
```

| Flag | Coverage Δ | |
|---|---|---|
| unittests | `52.55% <81.11%> (-35.33%)` | :arrow_down: |

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

| [Impacted Files](https://codecov.io/gh/spidernet-io/spiderpool/pull/1251?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spidernet-io) | Coverage Δ | |
|---|---|---|
| [pkg/reservedipmanager/reservedip\_webhook.go](https://codecov.io/gh/spidernet-io/spiderpool/pull/1251/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spidernet-io#diff-cGtnL3Jlc2VydmVkaXBtYW5hZ2VyL3Jlc2VydmVkaXBfd2ViaG9vay5nbw==) | `87.67% <ø> (ø)` | |
| [pkg/subnetmanager/subnet\_manager.go](https://codecov.io/gh/spidernet-io/spiderpool/pull/1251/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spidernet-io#diff-cGtnL3N1Ym5ldG1hbmFnZXIvc3VibmV0X21hbmFnZXIuZ28=) | `0.00% <0.00%> (ø)` | |
| [pkg/subnetmanager/subnet\_webhook.go](https://codecov.io/gh/spidernet-io/spiderpool/pull/1251/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spidernet-io#diff-cGtnL3N1Ym5ldG1hbmFnZXIvc3VibmV0X3dlYmhvb2suZ28=) | `45.07% <45.45%> (ø)` | |
| [pkg/subnetmanager/subnet\_validate.go](https://codecov.io/gh/spidernet-io/spiderpool/pull/1251/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spidernet-io#diff-cGtnL3N1Ym5ldG1hbmFnZXIvc3VibmV0X3ZhbGlkYXRlLmdv) | `49.40% <90.90%> (ø)` | |
| [pkg/reservedipmanager/reservedip\_validate.go](https://codecov.io/gh/spidernet-io/spiderpool/pull/1251/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spidernet-io#diff-cGtnL3Jlc2VydmVkaXBtYW5hZ2VyL3Jlc2VydmVkaXBfdmFsaWRhdGUuZ28=) | `100.00% <100.00%> (ø)` | |
| [pkg/subnetmanager/subnet\_mutate.go](https://codecov.io/gh/spidernet-io/spiderpool/pull/1251/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spidernet-io#diff-cGtnL3N1Ym5ldG1hbmFnZXIvc3VibmV0X211dGF0ZS5nbw==) | `100.00% <100.00%> (ø)` | |
| [pkg/workloadendpointmanager/config.go](https://codecov.io/gh/spidernet-io/spiderpool/pull/1251/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spidernet-io#diff-cGtnL3dvcmtsb2FkZW5kcG9pbnRtYW5hZ2VyL2NvbmZpZy5nbw==) | `100.00% <100.00%> (ø)` | |
| [...orkloadendpointmanager/workloadendpoint\_manager.go](https://codecov.io/gh/spidernet-io/spiderpool/pull/1251/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spidernet-io#diff-cGtnL3dvcmtsb2FkZW5kcG9pbnRtYW5hZ2VyL3dvcmtsb2FkZW5kcG9pbnRfbWFuYWdlci5nbw==) | `95.65% <100.00%> (ø)` | |
| [pkg/subnetmanager/app\_controller.go](https://codecov.io/gh/spidernet-io/spiderpool/pull/1251/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spidernet-io#diff-cGtnL3N1Ym5ldG1hbmFnZXIvYXBwX2NvbnRyb2xsZXIuZ28=) | `0.00% <0.00%> (ø)` | |
| ... and [3 more](https://codecov.io/gh/spidernet-io/spiderpool/pull/1251/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spidernet-io) | |

sonarcloud[bot]: SonarCloud Quality Gate failed.&nbsp; &nbsp; [![Quality Gate failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/failed-16px.png 'Quality Gate failed')](https://sonarcloud.io/dashboard?id=spidernet-io_spiderpool&pullRequest=1251)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=SECURITY_HOTSPOT) [![E](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/E-16px.png 'E')](https://sonarcloud.io/project/security_hotspots?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=SECURITY_HOTSPOT) [117 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=CODE_SMELL) [13 Code Smells](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=spidernet-io_spiderpool&pullRequest=1251) No Coverage information  
[![8.3%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/10-16px.png '8.3%')](https://sonarcloud.io/component_measures?id=spidernet-io_spiderpool&pullRequest=1251&metric=new_duplicated_lines_density&view=list) [8.3% Duplication](https://sonarcloud.io/component_measures?id=spidernet-io_spiderpool&pullRequest=1251&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: SonarCloud Quality Gate failed.&nbsp; &nbsp; [![Quality Gate failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/failed-16px.png 'Quality Gate failed')](https://sonarcloud.io/dashboard?id=spidernet-io_spiderpool&pullRequest=1251)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=SECURITY_HOTSPOT) [![E](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/E-16px.png 'E')](https://sonarcloud.io/project/security_hotspots?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=SECURITY_HOTSPOT) [125 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=CODE_SMELL) [12 Code Smells](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=spidernet-io_spiderpool&pullRequest=1251) No Coverage information  
[![8.5%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/10-16px.png '8.5%')](https://sonarcloud.io/component_measures?id=spidernet-io_spiderpool&pullRequest=1251&metric=new_duplicated_lines_density&view=list) [8.5% Duplication](https://sonarcloud.io/component_measures?id=spidernet-io_spiderpool&pullRequest=1251&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: SonarCloud Quality Gate failed.&nbsp; &nbsp; [![Quality Gate failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/failed-16px.png 'Quality Gate failed')](https://sonarcloud.io/dashboard?id=spidernet-io_spiderpool&pullRequest=1251)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=SECURITY_HOTSPOT) [![E](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/E-16px.png 'E')](https://sonarcloud.io/project/security_hotspots?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=SECURITY_HOTSPOT) [125 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=CODE_SMELL) [12 Code Smells](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=spidernet-io_spiderpool&pullRequest=1251) No Coverage information  
[![8.5%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/10-16px.png '8.5%')](https://sonarcloud.io/component_measures?id=spidernet-io_spiderpool&pullRequest=1251&metric=new_duplicated_lines_density&view=list) [8.5% Duplication](https://sonarcloud.io/component_measures?id=spidernet-io_spiderpool&pullRequest=1251&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: SonarCloud Quality Gate failed.&nbsp; &nbsp; [![Quality Gate failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/failed-16px.png 'Quality Gate failed')](https://sonarcloud.io/dashboard?id=spidernet-io_spiderpool&pullRequest=1251)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=SECURITY_HOTSPOT) [![E](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/E-16px.png 'E')](https://sonarcloud.io/project/security_hotspots?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=SECURITY_HOTSPOT) [125 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=CODE_SMELL) [14 Code Smells](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=spidernet-io_spiderpool&pullRequest=1251) No Coverage information  
[![8.5%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/10-16px.png '8.5%')](https://sonarcloud.io/component_measures?id=spidernet-io_spiderpool&pullRequest=1251&metric=new_duplicated_lines_density&view=list) [8.5% Duplication](https://sonarcloud.io/component_measures?id=spidernet-io_spiderpool&pullRequest=1251&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: SonarCloud Quality Gate failed.&nbsp; &nbsp; [![Quality Gate failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/failed-16px.png 'Quality Gate failed')](https://sonarcloud.io/dashboard?id=spidernet-io_spiderpool&pullRequest=1251)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=SECURITY_HOTSPOT) [![E](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/E-16px.png 'E')](https://sonarcloud.io/project/security_hotspots?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=SECURITY_HOTSPOT) [125 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=CODE_SMELL) [14 Code Smells](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=spidernet-io_spiderpool&pullRequest=1251) No Coverage information  
[![8.5%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/10-16px.png '8.5%')](https://sonarcloud.io/component_measures?id=spidernet-io_spiderpool&pullRequest=1251&metric=new_duplicated_lines_density&view=list) [8.5% Duplication](https://sonarcloud.io/component_measures?id=spidernet-io_spiderpool&pullRequest=1251&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: SonarCloud Quality Gate failed.&nbsp; &nbsp; [![Quality Gate failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/failed-16px.png 'Quality Gate failed')](https://sonarcloud.io/dashboard?id=spidernet-io_spiderpool&pullRequest=1251)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=SECURITY_HOTSPOT) [![E](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/E-16px.png 'E')](https://sonarcloud.io/project/security_hotspots?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=SECURITY_HOTSPOT) [125 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=CODE_SMELL) [14 Code Smells](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=spidernet-io_spiderpool&pullRequest=1251) No Coverage information  
[![8.5%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/10-16px.png '8.5%')](https://sonarcloud.io/component_measures?id=spidernet-io_spiderpool&pullRequest=1251&metric=new_duplicated_lines_density&view=list) [8.5% Duplication](https://sonarcloud.io/component_measures?id=spidernet-io_spiderpool&pullRequest=1251&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: SonarCloud Quality Gate failed.&nbsp; &nbsp; [![Quality Gate failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/failed-16px.png 'Quality Gate failed')](https://sonarcloud.io/dashboard?id=spidernet-io_spiderpool&pullRequest=1251)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=SECURITY_HOTSPOT) [![E](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/E-16px.png 'E')](https://sonarcloud.io/project/security_hotspots?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=SECURITY_HOTSPOT) [125 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=CODE_SMELL) [14 Code Smells](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1251&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=spidernet-io_spiderpool&pullRequest=1251) No Coverage information  
[![8.5%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/10-16px.png '8.5%')](https://sonarcloud.io/component_measures?id=spidernet-io_spiderpool&pullRequest=1251&metric=new_duplicated_lines_density&view=list) [8.5% Duplication](https://sonarcloud.io/component_measures?id=spidernet-io_spiderpool&pullRequest=1251&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/cmd/spiderpool-controller/cmd/daemon.go b/cmd/spiderpool-controller/cmd/daemon.go
index b717e92d6..5a7032431 100644
--- a/cmd/spiderpool-controller/cmd/daemon.go
+++ b/cmd/spiderpool-controller/cmd/daemon.go
@@ -288,14 +288,14 @@ func initControllerServiceManagers(ctx context.Context) {
 
 	logger.Debug("Begin to set up ReservedIP webhook")
 	if err := (&reservedipmanager.ReservedIPWebhook{
-		ReservedIPManager: controllerContext.RIPManager,
-		EnableIPv4:        controllerContext.Cfg.EnableIPv4,
-		EnableIPv6:        controllerContext.Cfg.EnableIPv6,
+		Client:     controllerContext.CRDManager.GetClient(),
+		EnableIPv4: controllerContext.Cfg.EnableIPv4,
+		EnableIPv6: controllerContext.Cfg.EnableIPv6,
 	}).SetupWebhookWithManager(controllerContext.CRDManager); err != nil {
 		logger.Fatal(err.Error())
 	}
 
-	logger.Info("Begin to initialize IPPool Manager")
+	logger.Debug("Begin to initialize IPPool manager")
 	ipPoolManager, err := ippoolmanager.NewIPPoolManager(&ippoolmanager.IPPoolManagerConfig{
 		EnableIPv4:                    controllerContext.Cfg.EnableIPv4,
 		EnableIPv6:                    controllerContext.Cfg.EnableIPv6,
@@ -314,21 +314,21 @@ func initControllerServiceManagers(ctx context.Context) {
 	controllerContext.IPPoolManager = ipPoolManager
 
 	go func() {
-		logger.Debug("Starting IPPool Manager")
+		logger.Debug("Starting IPPool manager")
 		if err := ipPoolManager.Start(controllerContext.InnerCtx); err != nil {
 			logger.Fatal(err.Error())
 		}
 	}()
 
 	// set up spiderpool controller IPPool webhook
-	logger.Info("Begin to set up SpiderIPPool webhook")
+	logger.Debug("Begin to set up IPPool webhook")
 	err = controllerContext.IPPoolManager.SetupWebhook()
 	if err != nil {
 		logger.Fatal(err.Error())
 	}
 
 	if controllerContext.Cfg.EnableSpiderSubnet {
-		logger.Info("Begin to initialize Subnet Manager")
+		logger.Debug("Begin to initialize Subnet manager")
 		subnetManager, err := subnetmanager.NewSubnetManager(&subnetmanager.SubnetManagerConfig{
 			EnableIPv4:                    controllerContext.Cfg.EnableIPv4,
 			EnableIPv6:                    controllerContext.Cfg.EnableIPv6,
@@ -348,9 +348,12 @@ func initControllerServiceManagers(ctx context.Context) {
 		controllerContext.SubnetManager = subnetManager
 		ipPoolManager.InjectSubnetManager(controllerContext.SubnetManager)
 
-		logger.Info("Begin to set up Subnet webhook")
-		err = controllerContext.SubnetManager.SetupWebhook()
-		if err != nil {
+		logger.Debug("Begin to set up Subnet webhook")
+		if err := (&subnetmanager.SubnetWebhook{
+			Client:     controllerContext.CRDManager.GetClient(),
+			EnableIPv4: controllerContext.Cfg.EnableIPv4,
+			EnableIPv6: controllerContext.Cfg.EnableIPv6,
+		}).SetupWebhookWithManager(controllerContext.CRDManager); err != nil {
 			logger.Fatal(err.Error())
 		}
 	} else {
diff --git a/pkg/reservedipmanager/mock/reservedip_manager_mock.go b/pkg/reservedipmanager/mock/reservedip_manager_mock.go
deleted file mode 100644
index ff22121ff..000000000
--- a/pkg/reservedipmanager/mock/reservedip_manager_mock.go
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright 2022 Authors of spidernet-io
-// SPDX-License-Identifier: Apache-2.0
-
-// Code generated by MockGen. DO NOT EDIT.
-// Source: ./reservedip_manager.go
-
-// Package mock_reservedipmanager is a generated GoMock package.
-package mock_reservedipmanager
-
-import (
-	context "context"
-	reflect "reflect"
-
-	gomock "github.com/golang/mock/gomock"
-	v1 "github.com/spidernet-io/spiderpool/pkg/k8s/apis/spiderpool.spidernet.io/v1"
-	client "sigs.k8s.io/controller-runtime/pkg/client"
-)
-
-// MockReservedIPManager is a mock of ReservedIPManager interface.
-type MockReservedIPManager struct {
-	ctrl     *gomock.Controller
-	recorder *MockReservedIPManagerMockRecorder
-}
-
-// MockReservedIPManagerMockRecorder is the mock recorder for MockReservedIPManager.
-type MockReservedIPManagerMockRecorder struct {
-	mock *MockReservedIPManager
-}
-
-// NewMockReservedIPManager creates a new mock instance.
-func NewMockReservedIPManager(ctrl *gomock.Controller) *MockReservedIPManager {
-	mock := &MockReservedIPManager{ctrl: ctrl}
-	mock.recorder = &MockReservedIPManagerMockRecorder{mock}
-	return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use.
-func (m *MockReservedIPManager) EXPECT() *MockReservedIPManagerMockRecorder {
-	return m.recorder
-}
-
-// GetReservedIPByName mocks base method.
-func (m *MockReservedIPManager) GetReservedIPByName(ctx context.Context, rIPName string) (*v1.SpiderReservedIP, error) {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "GetReservedIPByName", ctx, rIPName)
-	ret0, _ := ret[0].(*v1.SpiderReservedIP)
-	ret1, _ := ret[1].(error)
-	return ret0, ret1
-}
-
-// GetReservedIPByName indicates an expected call of GetReservedIPByName.
-func (mr *MockReservedIPManagerMockRecorder) GetReservedIPByName(ctx, rIPName interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetReservedIPByName", reflect.TypeOf((*MockReservedIPManager)(nil).GetReservedIPByName), ctx, rIPName)
-}
-
-// ListReservedIPs mocks base method.
-func (m *MockReservedIPManager) ListReservedIPs(ctx context.Context, opts ...client.ListOption) (*v1.SpiderReservedIPList, error) {
-	m.ctrl.T.Helper()
-	varargs := []interface{}{ctx}
-	for _, a := range opts {
-		varargs = append(varargs, a)
-	}
-	ret := m.ctrl.Call(m, "ListReservedIPs", varargs...)
-	ret0, _ := ret[0].(*v1.SpiderReservedIPList)
-	ret1, _ := ret[1].(error)
-	return ret0, ret1
-}
-
-// ListReservedIPs indicates an expected call of ListReservedIPs.
-func (mr *MockReservedIPManagerMockRecorder) ListReservedIPs(ctx interface{}, opts ...interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	varargs := append([]interface{}{ctx}, opts...)
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListReservedIPs", reflect.TypeOf((*MockReservedIPManager)(nil).ListReservedIPs), varargs...)
-}
diff --git a/pkg/reservedipmanager/reservedip_validate.go b/pkg/reservedipmanager/reservedip_validate.go
index b72777e1a..f046f0777 100644
--- a/pkg/reservedipmanager/reservedip_validate.go
+++ b/pkg/reservedipmanager/reservedip_validate.go
@@ -22,6 +22,10 @@ var (
 )
 
 func (rw *ReservedIPWebhook) validateCreateReservedIP(ctx context.Context, rIP *spiderpoolv1.SpiderReservedIP) field.ErrorList {
+	if err := rw.validateReservedIPIPVersion(rIP.Spec.IPVersion); err != nil {
+		return field.ErrorList{err}
+	}
+
 	var errs field.ErrorList
 	if err := rw.validateReservedIPSpec(ctx, rIP); err != nil {
 		errs = append(errs, err)
@@ -35,12 +39,16 @@ func (rw *ReservedIPWebhook) validateCreateReservedIP(ctx context.Context, rIP *
 }
 
 func (rw *ReservedIPWebhook) validateUpdateReservedIP(ctx context.Context, oldRIP, newRIP *spiderpoolv1.SpiderReservedIP) field.ErrorList {
-	var errs field.ErrorList
-	if err := rw.validateReservedIPSpec(ctx, newRIP); err != nil {
+	if err := validateReservedIPShouldNotBeChanged(oldRIP, newRIP); err != nil {
 		return field.ErrorList{err}
 	}
 
-	if err := validateReservedIPShouldNotBeChanged(oldRIP, newRIP); err != nil {
+	if err := rw.validateReservedIPIPVersion(newRIP.Spec.IPVersion); err != nil {
+		return field.ErrorList{err}
+	}
+
+	var errs field.ErrorList
+	if err := rw.validateReservedIPSpec(ctx, newRIP); err != nil {
 		errs = append(errs, err)
 	}
 
@@ -52,7 +60,8 @@ func (rw *ReservedIPWebhook) validateUpdateReservedIP(ctx context.Context, oldRI
 }
 
 func validateReservedIPShouldNotBeChanged(oldRIP, newRIP *spiderpoolv1.SpiderReservedIP) *field.Error {
-	if *newRIP.Spec.IPVersion != *oldRIP.Spec.IPVersion {
+	if newRIP.Spec.IPVersion != nil && oldRIP.Spec.IPVersion != nil &&
+		*newRIP.Spec.IPVersion != *oldRIP.Spec.IPVersion {
 		return field.Forbidden(
 			ipVersionField,
 			"is not changeable",
@@ -63,10 +72,6 @@ func validateReservedIPShouldNotBeChanged(oldRIP, newRIP *spiderpoolv1.SpiderRes
 }
 
 func (rw *ReservedIPWebhook) validateReservedIPSpec(ctx context.Context, rIP *spiderpoolv1.SpiderReservedIP) *field.Error {
-	if err := rw.validateReservedIPIPVersion(rIP.Spec.IPVersion); err != nil {
-		return err
-	}
-
 	return rw.validateReservedIPAvailableIP(ctx, *rIP.Spec.IPVersion, rIP)
 }
 
@@ -121,8 +126,8 @@ func (rw *ReservedIPWebhook) validateReservedIPAvailableIP(ctx context.Context,
 		)
 	}
 
-	rIPList, err := rw.ListReservedIPs(ctx)
-	if err != nil {
+	var rIPList spiderpoolv1.SpiderReservedIPList
+	if err := rw.List(ctx, &rIPList); err != nil {
 		return field.InternalError(ipsField, fmt.Errorf("failed to list ReservedIPs: %v", err))
 	}
 
diff --git a/pkg/reservedipmanager/reservedip_webhook.go b/pkg/reservedipmanager/reservedip_webhook.go
index 7532e496a..e76b827c4 100644
--- a/pkg/reservedipmanager/reservedip_webhook.go
+++ b/pkg/reservedipmanager/reservedip_webhook.go
@@ -12,6 +12,7 @@ import (
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/webhook"
 
 	"github.com/spidernet-io/spiderpool/pkg/constant"
@@ -22,21 +23,21 @@ import (
 var WebhookLogger *zap.Logger
 
 type ReservedIPWebhook struct {
-	ReservedIPManager
+	client.Client
 
 	EnableIPv4 bool
 	EnableIPv6 bool
 }
 
-func (rm *ReservedIPWebhook) SetupWebhookWithManager(mgr ctrl.Manager) error {
+func (rw *ReservedIPWebhook) SetupWebhookWithManager(mgr ctrl.Manager) error {
 	if WebhookLogger == nil {
 		WebhookLogger = logutils.Logger.Named("ReservedIP-Webhook")
 	}
 
 	return ctrl.NewWebhookManagedBy(mgr).
 		For(&spiderpoolv1.SpiderReservedIP{}).
-		WithDefaulter(rm).
-		WithValidator(rm).
+		WithDefaulter(rw).
+		WithValidator(rw).
 		Complete()
 }
 
diff --git a/pkg/subnetmanager/app_controller.go b/pkg/subnetmanager/app_controller.go
index d369f1eed..f6c93d7f2 100644
--- a/pkg/subnetmanager/app_controller.go
+++ b/pkg/subnetmanager/app_controller.go
@@ -607,7 +607,7 @@ func (c *appController) syncHandler(appKey appWorkQueueKey, log *zap.Logger) (er
 
 	var app metav1.Object
 	var subnetConfig *types.PodSubnetAnnoConfig
-	var podAnno map[string]string
+	var podAnno, podSelector map[string]string
 	var appReplicas int
 
 	switch appKey.AppKind {
@@ -622,6 +622,7 @@ func (c *appController) syncHandler(appKey appWorkQueueKey, log *zap.Logger) (er
 		}
 
 		podAnno = deployment.Spec.Template.Annotations
+		podSelector = deployment.Spec.Selector.MatchLabels
 		appReplicas = controllers.GetAppReplicas(deployment.Spec.Replicas)
 		app = deployment.DeepCopy()
 
@@ -636,6 +637,7 @@ func (c *appController) syncHandler(appKey appWorkQueueKey, log *zap.Logger) (er
 		}
 
 		podAnno = replicaSet.Spec.Template.Annotations
+		podSelector = replicaSet.Spec.Selector.MatchLabels
 		appReplicas = controllers.GetAppReplicas(replicaSet.Spec.Replicas)
 		app = replicaSet.DeepCopy()
 
@@ -650,6 +652,7 @@ func (c *appController) syncHandler(appKey appWorkQueueKey, log *zap.Logger) (er
 		}
 
 		podAnno = daemonSet.Spec.Template.Annotations
+		podSelector = daemonSet.Spec.Selector.MatchLabels
 		appReplicas = int(daemonSet.Status.DesiredNumberScheduled)
 		app = daemonSet.DeepCopy()
 
@@ -664,6 +667,7 @@ func (c *appController) syncHandler(appKey appWorkQueueKey, log *zap.Logger) (er
 		}
 
 		podAnno = statefulSet.Spec.Template.Annotations
+		podSelector = statefulSet.Spec.Selector.MatchLabels
 		appReplicas = controllers.GetAppReplicas(statefulSet.Spec.Replicas)
 		app = statefulSet.DeepCopy()
 
@@ -678,6 +682,7 @@ func (c *appController) syncHandler(appKey appWorkQueueKey, log *zap.Logger) (er
 		}
 
 		podAnno = job.Spec.Template.Annotations
+		podSelector = job.Spec.Selector.MatchLabels
 		appReplicas = controllers.CalculateJobPodNum(job.Spec.Parallelism, job.Spec.Completions)
 		app = job.DeepCopy()
 
@@ -692,6 +697,7 @@ func (c *appController) syncHandler(appKey appWorkQueueKey, log *zap.Logger) (er
 		}
 
 		podAnno = cronJob.Spec.JobTemplate.Spec.Template.Annotations
+		podSelector = cronJob.Spec.JobTemplate.Spec.Selector.MatchLabels
 		appReplicas = controllers.CalculateJobPodNum(cronJob.Spec.JobTemplate.Spec.Parallelism, cronJob.Spec.JobTemplate.Spec.Completions)
 		app = cronJob.DeepCopy()
 
@@ -705,7 +711,7 @@ func (c *appController) syncHandler(appKey appWorkQueueKey, log *zap.Logger) (er
 	}
 
 	log.Debug("Going to create IPPool or mark IPPool desired IP number")
-	err = c.createOrMarkIPPool(logutils.IntoContext(context.TODO(), log), *subnetConfig, appKey.AppKind, app, appReplicas)
+	err = c.createOrMarkIPPool(logutils.IntoContext(context.TODO(), log), *subnetConfig, appKey.AppKind, app, podSelector, appReplicas)
 	if nil != err {
 		return fmt.Errorf("failed to create or scale IPPool, error: %w", err)
 	}
@@ -714,8 +720,8 @@ func (c *appController) syncHandler(appKey appWorkQueueKey, log *zap.Logger) (er
 }
 
 // createOrMarkIPPool try to create an IPPool or mark IPPool desired IP number with the give SpiderSubnet configuration
-func (c *appController) createOrMarkIPPool(ctx context.Context, podSubnetConfig types.PodSubnetAnnoConfig,
-	appKind string, app metav1.Object, appReplicas int) error {
+func (c *appController) createOrMarkIPPool(ctx context.Context, podSubnetConfig types.PodSubnetAnnoConfig, appKind string, app metav1.Object,
+	podSelector map[string]string, appReplicas int) error {
 	log := logutils.FromContext(ctx)
 
 	// retrieve application pools
@@ -732,7 +738,7 @@ func (c *appController) createOrMarkIPPool(ctx context.Context, podSubnetConfig
 			log.Sugar().Debugf("there's no 'IPv%d' IPPoolList retrieved from SpiderSubent '%s'", ipVersion, subnetName)
 			// create an empty IPPool and mark the desired IP number when the subnet name was specified,
 			// and the IPPool informer will implement the scale action
-			err = c.subnetMgr.AllocateEmptyIPPool(ctx, subnetName, appKind, app, ipNum, ipVersion, podSubnetConfig.ReclaimIPPool, ifName)
+			err = c.subnetMgr.AllocateEmptyIPPool(ctx, subnetName, appKind, app, podSelector, ipNum, ipVersion, podSubnetConfig.ReclaimIPPool, ifName)
 		} else if len(poolList.Items) == 1 {
 			pool := poolList.Items[0]
 			log.Sugar().Debugf("found SpiderSubnet '%s' IPPool '%s' and check it whether need to be scaled", subnetName, pool.Name)
diff --git a/pkg/subnetmanager/subnet_manager.go b/pkg/subnetmanager/subnet_manager.go
index 3e08c169f..0050cb230 100644
--- a/pkg/subnetmanager/subnet_manager.go
+++ b/pkg/subnetmanager/subnet_manager.go
@@ -77,12 +77,12 @@ func (sm *subnetManager) GetSubnetByName(ctx context.Context, subnetName string)
 }
 
 func (sm *subnetManager) ListSubnets(ctx context.Context, opts ...client.ListOption) (*spiderpoolv1.SpiderSubnetList, error) {
-	subnetList := &spiderpoolv1.SpiderSubnetList{}
-	if err := sm.client.List(ctx, subnetList, opts...); err != nil {
+	var subnetList spiderpoolv1.SpiderSubnetList
+	if err := sm.client.List(ctx, &subnetList, opts...); err != nil {
 		return nil, err
 	}
 
-	return subnetList, nil
+	return &subnetList, nil
 }
 
 func (sm *subnetManager) GenerateIPsFromSubnetWhenScaleUpIP(ctx context.Context, subnetName string, pool *spiderpoolv1.SpiderIPPool, cursor bool) ([]string, error) {
@@ -209,7 +209,7 @@ func (sm *subnetManager) GenerateIPsFromSubnetWhenScaleUpIP(ctx context.Context,
 
 // AllocateEmptyIPPool will create an empty IPPool and mark the status.AutoDesiredIPCount
 // notice: this function only serves for auto-created IPPool
-func (sm *subnetManager) AllocateEmptyIPPool(ctx context.Context, subnetName string, appKind string, app metav1.Object,
+func (sm *subnetManager) AllocateEmptyIPPool(ctx context.Context, subnetName string, appKind string, app metav1.Object, podSelector map[string]string,
 	ipNum int, ipVersion types.IPVersion, reclaimIPPool bool, ifName string) error {
 	if len(subnetName) == 0 {
 		return fmt.Errorf("%w: spider subnet name must be specified", constant.ErrWrongInput)
@@ -254,6 +254,9 @@ func (sm *subnetManager) AllocateEmptyIPPool(ctx context.Context, subnetName str
 			Gateway: subnet.Spec.Gateway,
 			Vlan:    subnet.Spec.Vlan,
 			Routes:  subnet.Spec.Routes,
+			PodAffinity: &metav1.LabelSelector{
+				MatchLabels: podSelector,
+			},
 		},
 	}
 
diff --git a/pkg/subnetmanager/subnet_mutate.go b/pkg/subnetmanager/subnet_mutate.go
new file mode 100644
index 000000000..ec26ab629
--- /dev/null
+++ b/pkg/subnetmanager/subnet_mutate.go
@@ -0,0 +1,70 @@
+// Copyright 2022 Authors of spidernet-io
+// SPDX-License-Identifier: Apache-2.0
+
+package subnetmanager
+
+import (
+	"context"
+	"errors"
+	"fmt"
+
+	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
+
+	"github.com/spidernet-io/spiderpool/pkg/constant"
+	spiderpoolip "github.com/spidernet-io/spiderpool/pkg/ip"
+	spiderpoolv1 "github.com/spidernet-io/spiderpool/pkg/k8s/apis/spiderpool.spidernet.io/v1"
+	"github.com/spidernet-io/spiderpool/pkg/logutils"
+	"github.com/spidernet-io/spiderpool/pkg/types"
+)
+
+func (sw *SubnetWebhook) mutateSubnet(ctx context.Context, subnet *spiderpoolv1.SpiderSubnet) error {
+	logger := logutils.FromContext(ctx)
+	logger.Info("Start to mutate Subnet")
+
+	if subnet.DeletionTimestamp != nil {
+		logger.Info("Terminating Subnet, noting to mutate")
+		return nil
+	}
+
+	if !controllerutil.ContainsFinalizer(subnet, constant.SpiderFinalizer) {
+		controllerutil.AddFinalizer(subnet, constant.SpiderFinalizer)
+		logger.Sugar().Infof("Add finalizer %s", constant.SpiderFinalizer)
+	}
+
+	if subnet.Spec.IPVersion == nil {
+		var version types.IPVersion
+		if spiderpoolip.IsIPv4CIDR(subnet.Spec.Subnet) {
+			version = constant.IPv4
+		} else if spiderpoolip.IsIPv6CIDR(subnet.Spec.Subnet) {
+			version = constant.IPv6
+		} else {
+			return errors.New("invalid 'spec.ipVersion', noting to mutate")
+		}
+
+		subnet.Spec.IPVersion = new(types.IPVersion)
+		*subnet.Spec.IPVersion = version
+		logger.Sugar().Infof("Set 'spec.ipVersion' to %d", version)
+	}
+
+	if len(subnet.Spec.IPs) > 1 {
+		mergedIPs, err := spiderpoolip.MergeIPRanges(*subnet.Spec.IPVersion, subnet.Spec.IPs)
+		if err != nil {
+			return fmt.Errorf("failed to merge 'spec.ips': %v", err)
+		}
+
+		subnet.Spec.IPs = mergedIPs
+		logger.Sugar().Debugf("Merge 'spec.ips':\n%v\n\nto:\n\n%v", subnet.Spec.IPs, mergedIPs)
+	}
+
+	if len(subnet.Spec.ExcludeIPs) > 1 {
+		mergedExcludeIPs, err := spiderpoolip.MergeIPRanges(*subnet.Spec.IPVersion, subnet.Spec.ExcludeIPs)
+		if err != nil {
+			return fmt.Errorf("failed to merge 'spec.excludeIPs': %v", err)
+		}
+
+		subnet.Spec.ExcludeIPs = mergedExcludeIPs
+		logger.Sugar().Debugf("Merge 'spec.excludeIPs':\n%v\n\nto:\n\n%v", subnet.Spec.ExcludeIPs, mergedExcludeIPs)
+	}
+
+	return nil
+}
diff --git a/pkg/subnetmanager/subnet_validate.go b/pkg/subnetmanager/subnet_validate.go
index ea76c9a9f..3d8012683 100644
--- a/pkg/subnetmanager/subnet_validate.go
+++ b/pkg/subnetmanager/subnet_validate.go
@@ -27,9 +27,17 @@ var (
 	controlledIPPoolsField *field.Path = field.NewPath("status").Child("controlledIPPools")
 )
 
-func (sm *subnetManager) validateCreateSubnet(ctx context.Context, subnet *spiderpoolv1.SpiderSubnet) field.ErrorList {
+func (sw *SubnetWebhook) validateCreateSubnet(ctx context.Context, subnet *spiderpoolv1.SpiderSubnet) field.ErrorList {
+	if err := sw.validateSubnetIPVersion(subnet.Spec.IPVersion); err != nil {
+		return field.ErrorList{err}
+	}
+
+	if err := sw.validateSubnetSubnet(ctx, subnet); err != nil {
+		return field.ErrorList{err}
+	}
+
 	var errs field.ErrorList
-	if err := sm.validateSubnetSpec(ctx, subnet); err != nil {
+	if err := sw.validateSubnetSpec(ctx, subnet); err != nil {
 		errs = append(errs, err)
 	}
 
@@ -40,12 +48,16 @@ func (sm *subnetManager) validateCreateSubnet(ctx context.Context, subnet *spide
 	return errs
 }
 
-func (sm *subnetManager) validateUpdateSubnet(ctx context.Context, oldSubnet, newSubnet *spiderpoolv1.SpiderSubnet) field.ErrorList {
+func (sw *SubnetWebhook) validateUpdateSubnet(ctx context.Context, oldSubnet, newSubnet *spiderpoolv1.SpiderSubnet) field.ErrorList {
 	if err := validateSubnetShouldNotBeChanged(oldSubnet, newSubnet); err != nil {
 		return field.ErrorList{err}
 	}
 
-	if err := sm.validateSubnetSpec(ctx, newSubnet); err != nil {
+	if err := sw.validateSubnetIPVersion(newSubnet.Spec.IPVersion); err != nil {
+		return field.ErrorList{err}
+	}
+
+	if err := sw.validateSubnetSpec(ctx, newSubnet); err != nil {
 		return field.ErrorList{err}
 	}
 
@@ -62,7 +74,8 @@ func (sm *subnetManager) validateUpdateSubnet(ctx context.Context, oldSubnet, ne
 }
 
 func validateSubnetShouldNotBeChanged(oldSubnet, newSubnet *spiderpoolv1.SpiderSubnet) *field.Error {
-	if *newSubnet.Spec.IPVersion != *oldSubnet.Spec.IPVersion {
+	if newSubnet.Spec.IPVersion != nil && oldSubnet.Spec.IPVersion != nil &&
+		*newSubnet.Spec.IPVersion != *oldSubnet.Spec.IPVersion {
 		return field.Forbidden(
 			ipVersionField,
 			"is not changeable",
@@ -79,13 +92,7 @@ func validateSubnetShouldNotBeChanged(oldSubnet, newSubnet *spiderpoolv1.SpiderS
 	return nil
 }
 
-func (sm *subnetManager) validateSubnetSpec(ctx context.Context, subnet *spiderpoolv1.SpiderSubnet) *field.Error {
-	if err := sm.validateSubnetIPVersion(subnet.Spec.IPVersion); err != nil {
-		return err
-	}
-	if err := sm.validateSubnetSubnet(ctx, *subnet.Spec.IPVersion, subnet.Name, subnet.Spec.Subnet); err != nil {
-		return err
-	}
+func (sw *SubnetWebhook) validateSubnetSpec(ctx context.Context, subnet *spiderpoolv1.SpiderSubnet) *field.Error {
 	if err := validateSubnetIPs(*subnet.Spec.IPVersion, subnet.Spec.Subnet, subnet.Spec.IPs); err != nil {
 		return err
 	}
@@ -102,13 +109,13 @@ func (sm *subnetManager) validateSubnetSpec(ctx context.Context, subnet *spiderp
 func validateSubnetIPInUse(subnet *spiderpoolv1.SpiderSubnet) *field.Error {
 	totalIPs, err := spiderpoolip.AssembleTotalIPs(*subnet.Spec.IPVersion, subnet.Spec.IPs, subnet.Spec.ExcludeIPs)
 	if err != nil {
-		return field.InternalError(ipsField, err)
+		return field.InternalError(ipsField, fmt.Errorf("failed to assemble the total IP addresses of the Subnet %s: %v", subnet.Name, err))
 	}
 
 	for poolName, preAllocation := range subnet.Status.ControlledIPPools {
 		poolTotalIPs, err := spiderpoolip.ParseIPRanges(*subnet.Spec.IPVersion, preAllocation.IPs)
 		if err != nil {
-			return field.InternalError(controlledIPPoolsField, err)
+			return field.InternalError(controlledIPPoolsField, fmt.Errorf("failed to parse the pre-allocation of the IPPool %s: %v", poolName, err))
 		}
 		invalidIPs := spiderpoolip.IPsDiffSet(poolTotalIPs, totalIPs)
 		if len(invalidIPs) > 0 {
@@ -123,7 +130,7 @@ func validateSubnetIPInUse(subnet *spiderpoolv1.SpiderSubnet) *field.Error {
 	return nil
 }
 
-func (sm *subnetManager) validateSubnetIPVersion(version *types.IPVersion) *field.Error {
+func (sw *SubnetWebhook) validateSubnetIPVersion(version *types.IPVersion) *field.Error {
 	if version == nil {
 		return field.Invalid(
 			ipVersionField,
@@ -142,14 +149,14 @@ func (sm *subnetManager) validateSubnetIPVersion(version *types.IPVersion) *fiel
 		)
 	}
 
-	if *version == constant.IPv4 && !sm.config.EnableIPv4 {
+	if *version == constant.IPv4 && !sw.EnableIPv4 {
 		return field.Forbidden(
 			ipVersionField,
 			"IPv4 is disabled",
 		)
 	}
 
-	if *version == constant.IPv6 && !sm.config.EnableIPv6 {
+	if *version == constant.IPv6 && !sw.EnableIPv6 {
 		return field.Forbidden(
 			ipVersionField,
 			"IPv6 is disabled",
@@ -159,32 +166,38 @@ func (sm *subnetManager) validateSubnetIPVersion(version *types.IPVersion) *fiel
 	return nil
 }
 
-func (sm *subnetManager) validateSubnetSubnet(ctx context.Context, version types.IPVersion, subnetName, subnet string) *field.Error {
-	subnetList, err := sm.ListSubnets(ctx)
-	if err != nil {
-		return field.InternalError(subnetField, err)
+func (sw *SubnetWebhook) validateSubnetSubnet(ctx context.Context, subnet *spiderpoolv1.SpiderSubnet) *field.Error {
+	if err := spiderpoolip.IsCIDR(*subnet.Spec.IPVersion, subnet.Spec.Subnet); err != nil {
+		return field.Invalid(
+			subnetField,
+			subnet.Spec.Subnet,
+			err.Error(),
+		)
 	}
 
-	for _, s := range subnetList.Items {
-		if s.Name == subnetName || *s.Spec.IPVersion != version {
-			continue
-		}
-
-		overlap, err := spiderpoolip.IsCIDROverlap(version, subnet, s.Spec.Subnet)
-		if err != nil {
-			return field.Invalid(
-				subnetField,
-				subnet,
-				err.Error(),
-			)
-		}
+	subnetList := spiderpoolv1.SpiderSubnetList{}
+	if err := sw.List(ctx, &subnetList); err != nil {
+		return field.InternalError(subnetField, fmt.Errorf("failed to list Subnets: %v", err))
+	}
 
-		if overlap {
-			return field.Invalid(
-				subnetField,
-				subnet,
-				fmt.Sprintf("overlap with Subnet %s which 'spec.subnet' is %s", s.Name, s.Spec.Subnet),
-			)
+	for _, s := range subnetList.Items {
+		if *s.Spec.IPVersion == *subnet.Spec.IPVersion {
+			if s.Name == subnet.Name {
+				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)
+			if err != nil {
+				return field.InternalError(subnetField, fmt.Errorf("failed to compare whether 'spec.subnet' overlaps: %v", err))
+			}
+
+			if overlap {
+				return field.Invalid(
+					subnetField,
+					subnet.Spec.Subnet,
+					fmt.Sprintf("overlap with Subnet %s which 'spec.subnet' is %s", s.Name, s.Spec.Subnet),
+				)
+			}
 		}
 	}
 
@@ -192,8 +205,7 @@ func (sm *subnetManager) validateSubnetSubnet(ctx context.Context, version types
 }
 
 func validateSubnetIPs(version types.IPVersion, subnet string, ips []string) *field.Error {
-	n := len(ips)
-	if n == 0 {
+	if len(ips) == 0 {
 		return field.Required(
 			ipsField,
 			"requires at least one item",
diff --git a/pkg/subnetmanager/subnet_webhook.go b/pkg/subnetmanager/subnet_webhook.go
index 87df0d6bf..77943946c 100644
--- a/pkg/subnetmanager/subnet_webhook.go
+++ b/pkg/subnetmanager/subnet_webhook.go
@@ -6,116 +6,74 @@ package subnetmanager
 import (
 	"context"
 	"errors"
-	"fmt"
 
 	"go.uber.org/zap"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
 	"sigs.k8s.io/controller-runtime/pkg/webhook"
 
 	"github.com/spidernet-io/spiderpool/pkg/constant"
-	spiderpoolip "github.com/spidernet-io/spiderpool/pkg/ip"
 	spiderpoolv1 "github.com/spidernet-io/spiderpool/pkg/k8s/apis/spiderpool.spidernet.io/v1"
 	"github.com/spidernet-io/spiderpool/pkg/logutils"
-	"github.com/spidernet-io/spiderpool/pkg/types"
 )
 
-var webhookLogger *zap.Logger
+var WebhookLogger *zap.Logger
 
-func (sm *subnetManager) SetupWebhook() error {
-	webhookLogger = logutils.Logger.Named("Subnet-Webhook")
+type SubnetWebhook struct {
+	client.Client
 
-	return ctrl.NewWebhookManagedBy(sm.runtimeMgr).
+	EnableIPv4 bool
+	EnableIPv6 bool
+}
+
+func (sw *SubnetWebhook) SetupWebhookWithManager(mgr ctrl.Manager) error {
+	if WebhookLogger == nil {
+		WebhookLogger = logutils.Logger.Named("Subnet-Webhook")
+	}
+
+	return ctrl.NewWebhookManagedBy(mgr).
 		For(&spiderpoolv1.SpiderSubnet{}).
-		WithDefaulter(sm).
-		WithValidator(sm).
+		WithDefaulter(sw).
+		WithValidator(sw).
 		Complete()
 }
 
-var _ webhook.CustomDefaulter = (*subnetManager)(nil)
+var _ webhook.CustomDefaulter = (*SubnetWebhook)(nil)
 
-// Default implements webhook.CustomDefaulter so a webhook will be registered for the type
-func (sm *subnetManager) Default(ctx context.Context, obj runtime.Object) error {
-	subnet, ok := obj.(*spiderpoolv1.SpiderSubnet)
-	if !ok {
-		return apierrors.NewBadRequest(fmt.Sprintf("mutating webhook of Subnet got an object with mismatched GVK: %+v", obj.GetObjectKind().GroupVersionKind()))
-	}
+// Default implements webhook.CustomDefaulter so a webhook will be registered for the type.
+func (sw *SubnetWebhook) Default(ctx context.Context, obj runtime.Object) error {
+	subnet := obj.(*spiderpoolv1.SpiderSubnet)
 
-	logger := webhookLogger.Named("Mutating").With(
+	logger := WebhookLogger.Named("Mutating").With(
 		zap.String("SubnetName", subnet.Name),
 		zap.String("Operation", "DEFAULT"),
 	)
-	logger.Info("Start to mutate Subnet")
 	logger.Sugar().Debugf("Request Subnet: %+v", *subnet)
 
-	if subnet.DeletionTimestamp != nil {
-		logger.Info("Deleting Subnet, noting to mutate")
-		return nil
-	}
-
-	if !controllerutil.ContainsFinalizer(subnet, constant.SpiderFinalizer) {
-		controllerutil.AddFinalizer(subnet, constant.SpiderFinalizer)
-		logger.Sugar().Infof("Add finalizer %s", constant.SpiderFinalizer)
-	}
-
-	if subnet.Spec.IPVersion == nil {
-		var version types.IPVersion
-		if spiderpoolip.IsIPv4CIDR(subnet.Spec.Subnet) {
-			version = constant.IPv4
-		} else if spiderpoolip.IsIPv6CIDR(subnet.Spec.Subnet) {
-			version = constant.IPv6
-		} else {
-			logger.Error("Invalid 'spec.ipVersion', noting to mutate")
-			return nil
-		}
-
-		subnet.Spec.IPVersion = new(types.IPVersion)
-		*subnet.Spec.IPVersion = version
-		logger.Sugar().Infof("Set 'spec.ipVersion' to %d", version)
-	}
-
-	if len(subnet.Spec.IPs) > 1 {
-		mergedIPs, err := spiderpoolip.MergeIPRanges(*subnet.Spec.IPVersion, subnet.Spec.IPs)
-		if err != nil {
-			logger.Sugar().Errorf("Failed to merge 'spec.ips': %v", err)
-		} else {
-			subnet.Spec.IPs = mergedIPs
-			logger.Sugar().Debugf("Merge 'spec.ips':\n%v\n\nto:\n\n%v", subnet.Spec.IPs, mergedIPs)
-		}
-	}
-
-	if len(subnet.Spec.ExcludeIPs) > 1 {
-		mergedExcludeIPs, err := spiderpoolip.MergeIPRanges(*subnet.Spec.IPVersion, subnet.Spec.ExcludeIPs)
-		if err != nil {
-			logger.Sugar().Errorf("Failed to merge 'spec.excludeIPs': %v", err)
-		} else {
-			subnet.Spec.ExcludeIPs = mergedExcludeIPs
-			logger.Sugar().Debugf("Merge 'spec.excludeIPs':\n%v\n\nto:\n\n%v", subnet.Spec.ExcludeIPs, mergedExcludeIPs)
-		}
+	if err := sw.mutateSubnet(logutils.IntoContext(ctx, logger), subnet); err != nil {
+		logger.Sugar().Errorf("Failed to mutate Subnet: %v", err)
 	}
 
 	return nil
 }
 
-var _ webhook.CustomValidator = (*subnetManager)(nil)
+var _ webhook.CustomValidator = (*SubnetWebhook)(nil)
 
-// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type
-func (sm *subnetManager) ValidateCreate(ctx context.Context, obj runtime.Object) error {
-	subnet, ok := obj.(*spiderpoolv1.SpiderSubnet)
-	if !ok {
-		return apierrors.NewBadRequest(fmt.Sprintf("validating webhook of Subnet got an object with mismatched GVK: %+v", obj.GetObjectKind().GroupVersionKind()))
-	}
+// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type.
+func (sw *SubnetWebhook) ValidateCreate(ctx context.Context, obj runtime.Object) error {
+	subnet := obj.(*spiderpoolv1.SpiderSubnet)
 
-	logger := webhookLogger.Named("Validating").With(
+	logger := WebhookLogger.Named("Validating").With(
 		zap.String("SubnetName", subnet.Name),
 		zap.String("Operation", "CREATE"),
 	)
 	logger.Sugar().Debugf("Request Subnet: %+v", *subnet)
 
-	if errs := sm.validateCreateSubnet(logutils.IntoContext(ctx, logger), subnet); len(errs) != 0 {
+	if errs := sw.validateCreateSubnet(logutils.IntoContext(ctx, logger), subnet); len(errs) != 0 {
 		logger.Sugar().Errorf("Failed to create Subnet: %v", errs.ToAggregate().Error())
 		return apierrors.NewInvalid(
 			schema.GroupKind{Group: constant.SpiderpoolAPIGroup, Kind: constant.SpiderSubnetKind},
@@ -127,15 +85,12 @@ func (sm *subnetManager) ValidateCreate(ctx context.Context, obj runtime.Object)
 	return nil
 }
 
-// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type
-func (sm *subnetManager) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) error {
-	oldSubnet, _ := oldObj.(*spiderpoolv1.SpiderSubnet)
-	newSubnet, ok := newObj.(*spiderpoolv1.SpiderSubnet)
-	if !ok {
-		return apierrors.NewBadRequest(fmt.Sprintf("validating webhook of Subnet got an object with mismatched GVK: %+v", newObj.GetObjectKind().GroupVersionKind()))
-	}
+// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type.
+func (sw *SubnetWebhook) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) error {
+	oldSubnet := oldObj.(*spiderpoolv1.SpiderSubnet)
+	newSubnet := newObj.(*spiderpoolv1.SpiderSubnet)
 
-	logger := webhookLogger.Named("Validating").With(
+	logger := WebhookLogger.Named("Validating").With(
 		zap.String("SubnetName", newSubnet.Name),
 		zap.String("Operation", "UPDATE"),
 	)
@@ -154,7 +109,7 @@ func (sm *subnetManager) ValidateUpdate(ctx context.Context, oldObj, newObj runt
 		)
 	}
 
-	if errs := sm.validateUpdateSubnet(logutils.IntoContext(ctx, logger), oldSubnet, newSubnet); len(errs) != 0 {
+	if errs := sw.validateUpdateSubnet(logutils.IntoContext(ctx, logger), oldSubnet, newSubnet); len(errs) != 0 {
 		logger.Sugar().Errorf("Failed to update Subnet: %v", errs.ToAggregate().Error())
 		return apierrors.NewInvalid(
 			schema.GroupKind{Group: constant.SpiderpoolAPIGroup, Kind: constant.SpiderSubnetKind},
@@ -166,7 +121,7 @@ func (sm *subnetManager) ValidateUpdate(ctx context.Context, oldObj, newObj runt
 	return nil
 }
 
-// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type
-func (sm *subnetManager) ValidateDelete(ctx context.Context, obj runtime.Object) error {
+// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type.
+func (sw *SubnetWebhook) ValidateDelete(ctx context.Context, obj runtime.Object) error {
 	return nil
 }
diff --git a/pkg/subnetmanager/types/interface.go b/pkg/subnetmanager/types/interface.go
index 1231ed5a7..5c24ebad6 100644
--- a/pkg/subnetmanager/types/interface.go
+++ b/pkg/subnetmanager/types/interface.go
@@ -17,12 +17,11 @@ import (
 )
 
 type SubnetManager interface {
-	SetupWebhook() error
 	SetupInformer(ctx context.Context, client crdclientset.Interface, controllerLeader election.SpiderLeaseElector) error
 	GetSubnetByName(ctx context.Context, subnetName string) (*spiderpoolv1.SpiderSubnet, error)
 	ListSubnets(ctx context.Context, opts ...client.ListOption) (*spiderpoolv1.SpiderSubnetList, error)
 	SetupControllers(client kubernetes.Interface) error
-	AllocateEmptyIPPool(ctx context.Context, subnetMgrName string, appKind string, app metav1.Object, ipNum int, ipVersion types.IPVersion, reclaimIPPool bool, ifName string) error
+	AllocateEmptyIPPool(ctx context.Context, subnetMgrName string, appKind string, app metav1.Object, podSelector map[string]string, ipNum int, ipVersion types.IPVersion, reclaimIPPool bool, ifName string) error
 	CheckScaleIPPool(ctx context.Context, pool *spiderpoolv1.SpiderIPPool, subnetManagerName string, ipNum int) error
 	GenerateIPsFromSubnetWhenScaleUpIP(ctx context.Context, subnetName string, pool *spiderpoolv1.SpiderIPPool, cursor bool) ([]string, error)
 }
diff --git a/pkg/workloadendpointmanager/config.go b/pkg/workloadendpointmanager/config.go
index 2bf95a293..07357de69 100644
--- a/pkg/workloadendpointmanager/config.go
+++ b/pkg/workloadendpointmanager/config.go
@@ -22,7 +22,7 @@ type EndpointManagerConfig struct {
 	MaxHistoryRecords     *int
 }
 
-func setDefaultsForPodManagerConfig(config EndpointManagerConfig) EndpointManagerConfig {
+func setDefaultsForEndpointManagerConfig(config EndpointManagerConfig) EndpointManagerConfig {
 	if config.scheme == nil {
 		config.scheme = runtime.NewScheme()
 		utilruntime.Must(corev1.AddToScheme(config.scheme))
diff --git a/pkg/workloadendpointmanager/workloadendpoint_manager.go b/pkg/workloadendpointmanager/workloadendpoint_manager.go
index a8d8d4936..69481de3a 100644
--- a/pkg/workloadendpointmanager/workloadendpoint_manager.go
+++ b/pkg/workloadendpointmanager/workloadendpoint_manager.go
@@ -50,7 +50,7 @@ func NewWorkloadEndpointManager(config EndpointManagerConfig, client client.Clie
 	}
 
 	return &workloadEndpointManager{
-		config:     setDefaultsForPodManagerConfig(config),
+		config:     setDefaultsForEndpointManagerConfig(config),
 		client:     client,
 		podManager: podManager,
 	}, nil


Test patch:
diff --git a/pkg/reservedipmanager/reservedip_manager_suite_test.go b/pkg/reservedipmanager/reservedip_manager_suite_test.go
index 5a378d117..8cd163591 100644
--- a/pkg/reservedipmanager/reservedip_manager_suite_test.go
+++ b/pkg/reservedipmanager/reservedip_manager_suite_test.go
@@ -6,7 +6,6 @@ package reservedipmanager_test
 import (
 	"testing"
 
-	"github.com/golang/mock/gomock"
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
 	"k8s.io/apimachinery/pkg/runtime"
@@ -15,21 +14,14 @@ import (
 
 	spiderpoolv1 "github.com/spidernet-io/spiderpool/pkg/k8s/apis/spiderpool.spidernet.io/v1"
 	"github.com/spidernet-io/spiderpool/pkg/reservedipmanager"
-	mock "github.com/spidernet-io/spiderpool/pkg/reservedipmanager/mock"
 )
 
-var mockCtrl *gomock.Controller
-var mockRIPManager *mock.MockReservedIPManager
-
 var scheme *runtime.Scheme
 var fakeClient client.Client
 var rIPManager reservedipmanager.ReservedIPManager
 var rIPWebhook *reservedipmanager.ReservedIPWebhook
 
 func TestReservedIPManager(t *testing.T) {
-	mockCtrl = gomock.NewController(t)
-	defer mockCtrl.Finish()
-
 	RegisterFailHandler(Fail)
 	RunSpecs(t, "ReservedIPManager Suite", Label("reservedipmanager", "unitest"))
 }
@@ -46,8 +38,7 @@ var _ = BeforeSuite(func() {
 	rIPManager, err = reservedipmanager.NewReservedIPManager(fakeClient)
 	Expect(err).NotTo(HaveOccurred())
 
-	mockRIPManager = mock.NewMockReservedIPManager(mockCtrl)
 	rIPWebhook = &reservedipmanager.ReservedIPWebhook{
-		ReservedIPManager: mockRIPManager,
+		Client: fakeClient,
 	}
 })
diff --git a/pkg/reservedipmanager/reservedip_webhook_test.go b/pkg/reservedipmanager/reservedip_webhook_test.go
index a6c3a3fa1..22cf9b01b 100644
--- a/pkg/reservedipmanager/reservedip_webhook_test.go
+++ b/pkg/reservedipmanager/reservedip_webhook_test.go
@@ -6,15 +6,16 @@ package reservedipmanager_test
 import (
 	"context"
 	"fmt"
-	"time"
+	"sync/atomic"
 
-	"github.com/golang/mock/gomock"
+	"github.com/agiledragon/gomonkey/v2"
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/utils/pointer"
 	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/client/config"
 	"sigs.k8s.io/controller-runtime/pkg/manager"
 
@@ -40,35 +41,62 @@ var _ = Describe("ReservedIPWebhook", Label("reservedip_webhook_test"), func() {
 	})
 
 	Describe("Test ReservedIPWebhook's method", func() {
-		var rIPT *spiderpoolv1.SpiderReservedIP
-		var rIPListT *spiderpoolv1.SpiderReservedIPList
+		var count uint64
+		var rIPName, existRIPName string
+		var rIPT, existRIPT *spiderpoolv1.SpiderReservedIP
 
 		BeforeEach(func() {
 			reservedipmanager.WebhookLogger = logutils.Logger.Named("ReservedIP-Webhook")
+			rIPWebhook.EnableIPv4 = true
+			rIPWebhook.EnableIPv6 = true
+
+			atomic.AddUint64(&count, 1)
+			rIPName = fmt.Sprintf("reservedip-%v", count)
 			rIPT = &spiderpoolv1.SpiderReservedIP{
 				TypeMeta: metav1.TypeMeta{
 					Kind:       constant.SpiderReservedIPKind,
 					APIVersion: fmt.Sprintf("%s/%s", constant.SpiderpoolAPIGroup, constant.SpiderpoolAPIVersionV1),
 				},
 				ObjectMeta: metav1.ObjectMeta{
-					Name: "reservedip",
+					Name: rIPName,
 				},
 				Spec: spiderpoolv1.ReservedIPSpec{},
 			}
 
-			rIPListT = &spiderpoolv1.SpiderReservedIPList{
+			existRIPName = fmt.Sprintf("z-exist-reservedip-%v", count)
+			existRIPT = &spiderpoolv1.SpiderReservedIP{
 				TypeMeta: metav1.TypeMeta{
-					Kind:       constant.SpiderReservedIPListKind,
+					Kind:       constant.SpiderReservedIPKind,
 					APIVersion: fmt.Sprintf("%s/%s", constant.SpiderpoolAPIGroup, constant.SpiderpoolAPIVersionV1),
 				},
-				ListMeta: metav1.ListMeta{},
+				ObjectMeta: metav1.ObjectMeta{
+					Name: existRIPName,
+				},
+				Spec: spiderpoolv1.ReservedIPSpec{},
 			}
 		})
 
+		var deleteOption *client.DeleteOptions
+
+		AfterEach(func() {
+			policy := metav1.DeletePropagationForeground
+			deleteOption = &client.DeleteOptions{
+				GracePeriodSeconds: pointer.Int64(0),
+				PropagationPolicy:  &policy,
+			}
+
+			ctx := context.TODO()
+			err := fakeClient.Delete(ctx, rIPT, deleteOption)
+			Expect(client.IgnoreNotFound(err)).NotTo(HaveOccurred())
+
+			err = fakeClient.Delete(ctx, existRIPT, deleteOption)
+			Expect(client.IgnoreNotFound(err)).NotTo(HaveOccurred())
+		})
+
 		Describe("Default", func() {
 			It("avoids modifying the terminating ReservedIP", func() {
-				deletionTimestamp := metav1.NewTime(time.Now().Add(30 * time.Second))
-				rIPT.SetDeletionTimestamp(&deletionTimestamp)
+				now := metav1.Now()
+				rIPT.SetDeletionTimestamp(&now)
 
 				ctx := context.TODO()
 				err := rIPWebhook.Default(ctx, rIPT)
@@ -109,8 +137,7 @@ var _ = Describe("ReservedIPWebhook", Label("reservedip_webhook_test"), func() {
 			})
 
 			It("failed to merge 'spec.ips' due to the invalid 'spec.ipVersion'", func() {
-				ipVersion := constant.InvalidIPVersion
-				rIPT.Spec.IPVersion = &ipVersion
+				rIPT.Spec.IPVersion = pointer.Int64(constant.InvalidIPVersion)
 				rIPT.Spec.IPs = append(rIPT.Spec.IPs,
 					[]string{
 						"172.18.40.10",
@@ -132,8 +159,7 @@ var _ = Describe("ReservedIPWebhook", Label("reservedip_webhook_test"), func() {
 			})
 
 			It("failed to merge 'spec.ips' due to the invalid 'spec.ips'", func() {
-				ipv4 := constant.IPv4
-				rIPT.Spec.IPVersion = &ipv4
+				rIPT.Spec.IPVersion = pointer.Int64(constant.IPv4)
 				rIPT.Spec.IPs = append(rIPT.Spec.IPs,
 					[]string{
 						constant.InvalidIPRange,
@@ -157,8 +183,7 @@ var _ = Describe("ReservedIPWebhook", Label("reservedip_webhook_test"), func() {
 			})
 
 			It("merges IPv4 'spec.ips'", func() {
-				ipv4 := constant.IPv4
-				rIPT.Spec.IPVersion = &ipv4
+				rIPT.Spec.IPVersion = pointer.Int64(constant.IPv4)
 				rIPT.Spec.IPs = append(rIPT.Spec.IPs,
 					[]string{
 						"172.18.40.10",
@@ -179,8 +204,7 @@ var _ = Describe("ReservedIPWebhook", Label("reservedip_webhook_test"), func() {
 			})
 
 			It("merges IPv6 'spec.ips'", func() {
-				ipv6 := constant.IPv6
-				rIPT.Spec.IPVersion = &ipv6
+				rIPT.Spec.IPVersion = pointer.Int64(constant.IPv6)
 				rIPT.Spec.IPs = append(rIPT.Spec.IPs,
 					[]string{
 						"abcd:1234::a",
@@ -202,11 +226,6 @@ var _ = Describe("ReservedIPWebhook", Label("reservedip_webhook_test"), func() {
 		})
 
 		Describe("ValidateCreate", func() {
-			BeforeEach(func() {
-				rIPWebhook.EnableIPv4 = true
-				rIPWebhook.EnableIPv6 = true
-			})
-
 			When("Validating 'spec.ipVersion'", func() {
 				It("inputs nil 'spec.ipVersion'", func() {
 					ctx := context.TODO()
@@ -215,8 +234,7 @@ var _ = Describe("ReservedIPWebhook", Label("reservedip_webhook_test"), func() {
 				})
 
 				It("inputs invalid 'spec.ipVersion'", func() {
-					ipVersion := constant.InvalidIPVersion
-					rIPT.Spec.IPVersion = &ipVersion
+					rIPT.Spec.IPVersion = pointer.Int64(constant.InvalidIPVersion)
 
 					ctx := context.TODO()
 					err := rIPWebhook.ValidateCreate(ctx, rIPT)
@@ -225,8 +243,7 @@ var _ = Describe("ReservedIPWebhook", Label("reservedip_webhook_test"), func() {
 
 				It("creates IPv4 ReservedIP but IPv4 is disbale'", func() {
 					rIPWebhook.EnableIPv4 = false
-					ipVersion := constant.IPv4
-					rIPT.Spec.IPVersion = &ipVersion
+					rIPT.Spec.IPVersion = pointer.Int64(constant.IPv4)
 
 					ctx := context.TODO()
 					err := rIPWebhook.ValidateCreate(ctx, rIPT)
@@ -235,8 +252,7 @@ var _ = Describe("ReservedIPWebhook", Label("reservedip_webhook_test"), func() {
 
 				It("creates IPv6 ReservedIP but IPv6 is disbale'", func() {
 					rIPWebhook.EnableIPv6 = false
-					ipVersion := constant.IPv6
-					rIPT.Spec.IPVersion = &ipVersion
+					rIPT.Spec.IPVersion = pointer.Int64(constant.IPv6)
 
 					ctx := context.TODO()
 					err := rIPWebhook.ValidateCreate(ctx, rIPT)
@@ -246,8 +262,7 @@ var _ = Describe("ReservedIPWebhook", Label("reservedip_webhook_test"), func() {
 
 			When("Validating 'spec.ips'", func() {
 				It("inputs empty 'spec.ips'", func() {
-					ipVersion := constant.IPv4
-					rIPT.Spec.IPVersion = &ipVersion
+					rIPT.Spec.IPVersion = pointer.Int64(constant.IPv4)
 
 					ctx := context.TODO()
 					err := rIPWebhook.ValidateCreate(ctx, rIPT)
@@ -255,8 +270,7 @@ var _ = Describe("ReservedIPWebhook", Label("reservedip_webhook_test"), func() {
 				})
 
 				It("inputs invalid 'spec.ips'", func() {
-					ipVersion := constant.IPv4
-					rIPT.Spec.IPVersion = &ipVersion
+					rIPT.Spec.IPVersion = pointer.Int64(constant.IPv4)
 					rIPT.Spec.IPs = append(rIPT.Spec.IPs, constant.InvalidIPRange)
 
 					ctx := context.TODO()
@@ -265,13 +279,10 @@ var _ = Describe("ReservedIPWebhook", Label("reservedip_webhook_test"), func() {
 				})
 
 				It("failed to list ReservedIPs due to some unknown errors", func() {
-					mockRIPManager.EXPECT().
-						ListReservedIPs(gomock.All()).
-						Return(nil, constant.ErrUnknown).
-						Times(1)
+					patches := gomonkey.ApplyMethodReturn(fakeClient, "List", constant.ErrUnknown)
+					defer patches.Reset()
 
-					ipVersion := constant.IPv4
-					rIPT.Spec.IPVersion = &ipVersion
+					rIPT.Spec.IPVersion = pointer.Int64(constant.IPv4)
 					rIPT.Spec.IPs = append(rIPT.Spec.IPs,
 						[]string{
 							"172.18.40.1-172.18.40.2",
@@ -285,19 +296,14 @@ var _ = Describe("ReservedIPWebhook", Label("reservedip_webhook_test"), func() {
 				})
 
 				It("exists invalid ReservedIPs in the cluster", func() {
-					existRIPT := rIPT.DeepCopy()
-					existRIPT.Name = "exist-reservedip"
-					ipVersion := constant.IPv4
-					existRIPT.Spec.IPVersion = &ipVersion
+					existRIPT.Spec.IPVersion = pointer.Int64(constant.IPv4)
 					existRIPT.Spec.IPs = append(existRIPT.Spec.IPs, constant.InvalidIPRange)
-					rIPListT.Items = append(rIPListT.Items, *existRIPT)
 
-					mockRIPManager.EXPECT().
-						ListReservedIPs(gomock.All()).
-						Return(rIPListT, nil).
-						Times(1)
+					ctx := context.TODO()
+					err := fakeClient.Create(ctx, existRIPT)
+					Expect(err).NotTo(HaveOccurred())
 
-					rIPT.Spec.IPVersion = &ipVersion
+					rIPT.Spec.IPVersion = pointer.Int64(constant.IPv4)
 					rIPT.Spec.IPs = append(rIPT.Spec.IPs,
 						[]string{
 							"172.18.40.1-172.18.40.2",
@@ -305,25 +311,19 @@ var _ = Describe("ReservedIPWebhook", Label("reservedip_webhook_test"), func() {
 						}...,
 					)
 
-					ctx := context.TODO()
-					err := rIPWebhook.ValidateCreate(ctx, rIPT)
+					err = rIPWebhook.ValidateCreate(ctx, rIPT)
 					Expect(apierrors.IsInvalid(err)).To(BeTrue())
 				})
 
-				It("overlaps with the existing ReservedIP", func() {
-					existRIPT := rIPT.DeepCopy()
-					existRIPT.Name = "exist-reservedip"
-					ipVersion := constant.IPv4
-					existRIPT.Spec.IPVersion = &ipVersion
+				It("overlaps with existing ReservedIP", func() {
+					existRIPT.Spec.IPVersion = pointer.Int64(constant.IPv4)
 					existRIPT.Spec.IPs = append(existRIPT.Spec.IPs, "172.18.40.10")
-					rIPListT.Items = append(rIPListT.Items, *existRIPT)
 
-					mockRIPManager.EXPECT().
-						ListReservedIPs(gomock.All()).
-						Return(rIPListT, nil).
-						Times(1)
+					ctx := context.TODO()
+					err := fakeClient.Create(ctx, existRIPT)
+					Expect(err).NotTo(HaveOccurred())
 
-					rIPT.Spec.IPVersion = &ipVersion
+					rIPT.Spec.IPVersion = pointer.Int64(constant.IPv4)
 					rIPT.Spec.IPs = append(rIPT.Spec.IPs,
 						[]string{
 							"172.18.40.1-172.18.40.2",
@@ -331,20 +331,13 @@ var _ = Describe("ReservedIPWebhook", Label("reservedip_webhook_test"), func() {
 						}...,
 					)
 
-					ctx := context.TODO()
-					err := rIPWebhook.ValidateCreate(ctx, rIPT)
+					err = rIPWebhook.ValidateCreate(ctx, rIPT)
 					Expect(apierrors.IsInvalid(err)).To(BeTrue())
 				})
 			})
 
 			It("creates IPv4 ReservedIP with all fields valid", func() {
-				mockRIPManager.EXPECT().
-					ListReservedIPs(gomock.All()).
-					Return(rIPListT, nil).
-					Times(1)
-
-				ipVersion := constant.IPv4
-				rIPT.Spec.IPVersion = &ipVersion
+				rIPT.Spec.IPVersion = pointer.Int64(constant.IPv4)
 				rIPT.Spec.IPs = append(rIPT.Spec.IPs,
 					[]string{
 						"172.18.40.1-172.18.40.2",
@@ -358,13 +351,7 @@ var _ = Describe("ReservedIPWebhook", Label("reservedip_webhook_test"), func() {
 			})
 
 			It("creates IPv6 ReservedIP with all fields valid", func() {
-				mockRIPManager.EXPECT().
-					ListReservedIPs(gomock.All()).
-					Return(rIPListT, nil).
-					Times(1)
-
-				ipVersion := constant.IPv6
-				rIPT.Spec.IPVersion = &ipVersion
+				rIPT.Spec.IPVersion = pointer.Int64(constant.IPv6)
 				rIPT.Spec.IPs = append(rIPT.Spec.IPs,
 					[]string{
 						"abcd:1234::1-abcd:1234::2",
@@ -379,16 +366,9 @@ var _ = Describe("ReservedIPWebhook", Label("reservedip_webhook_test"), func() {
 		})
 
 		Describe("ValidateUpdate", func() {
-			BeforeEach(func() {
-				rIPWebhook.EnableIPv4 = true
-				rIPWebhook.EnableIPv6 = true
-			})
-
 			When("Validating 'spec.ipVersion'", func() {
 				It("updates 'spec.ipVersion' to nil", func() {
-					rIPWebhook.EnableIPv4 = false
-					ipVersion := constant.IPv4
-					rIPT.Spec.IPVersion = &ipVersion
+					rIPT.Spec.IPVersion = pointer.Int64(constant.IPv4)
 
 					newRIPT := rIPT.DeepCopy()
 					newRIPT.Spec.IPVersion = nil
@@ -398,28 +378,11 @@ var _ = Describe("ReservedIPWebhook", Label("reservedip_webhook_test"), func() {
 					Expect(apierrors.IsInvalid(err)).To(BeTrue())
 				})
 
-				It("updates 'spec.ipVersion' to invalid IP version", func() {
-					rIPWebhook.EnableIPv4 = false
-					ipVersion := constant.IPv4
-					rIPT.Spec.IPVersion = &ipVersion
-
-					newRIPT := rIPT.DeepCopy()
-					invalidIPVersion := constant.InvalidIPVersion
-					newRIPT.Spec.IPVersion = &invalidIPVersion
-
-					ctx := context.TODO()
-					err := rIPWebhook.ValidateUpdate(ctx, rIPT, newRIPT)
-					Expect(apierrors.IsInvalid(err)).To(BeTrue())
-				})
-
 				It("changes 'spec.ipVersion'", func() {
-					rIPWebhook.EnableIPv4 = false
-					ipv4 := constant.IPv4
-					rIPT.Spec.IPVersion = &ipv4
+					rIPT.Spec.IPVersion = pointer.Int64(constant.IPv4)
 
 					newRIPT := rIPT.DeepCopy()
-					ipv6 := constant.IPv6
-					newRIPT.Spec.IPVersion = &ipv6
+					newRIPT.Spec.IPVersion = pointer.Int64(constant.IPv6)
 
 					ctx := context.TODO()
 					err := rIPWebhook.ValidateUpdate(ctx, rIPT, newRIPT)
@@ -428,8 +391,7 @@ var _ = Describe("ReservedIPWebhook", Label("reservedip_webhook_test"), func() {
 
 				It("updates IPv4 ReservedIP but IPv4 is disbale'", func() {
 					rIPWebhook.EnableIPv4 = false
-					ipVersion := constant.IPv4
-					rIPT.Spec.IPVersion = &ipVersion
+					rIPT.Spec.IPVersion = pointer.Int64(constant.IPv4)
 
 					newRIPT := rIPT.DeepCopy()
 					newRIPT.Spec.IPs = append(newRIPT.Spec.IPs, "172.18.40.10")
@@ -441,8 +403,7 @@ var _ = Describe("ReservedIPWebhook", Label("reservedip_webhook_test"), func() {
 
 				It("updates IPv6 ReservedIP but IPv6 is disbale'", func() {
 					rIPWebhook.EnableIPv6 = false
-					ipVersion := constant.IPv6
-					rIPT.Spec.IPVersion = &ipVersion
+					rIPT.Spec.IPVersion = pointer.Int64(constant.IPv6)
 
 					newRIPT := rIPT.DeepCopy()
 					newRIPT.Spec.IPs = append(newRIPT.Spec.IPs, "adbc:1234::a")
@@ -454,9 +415,8 @@ var _ = Describe("ReservedIPWebhook", Label("reservedip_webhook_test"), func() {
 			})
 
 			When("Validating 'spec.ips'", func() {
-				It("appends an invalid IP range to 'spec.ips'", func() {
-					ipVersion := constant.IPv4
-					rIPT.Spec.IPVersion = &ipVersion
+				It("appends invalid IP range to 'spec.ips'", func() {
+					rIPT.Spec.IPVersion = pointer.Int64(constant.IPv4)
 
 					newRIPT := rIPT.DeepCopy()
 					newRIPT.Spec.IPs = append(newRIPT.Spec.IPs, constant.InvalidIPRange)
@@ -467,13 +427,10 @@ var _ = Describe("ReservedIPWebhook", Label("reservedip_webhook_test"), func() {
 				})
 
 				It("failed to list ReservedIPs due to some unknown errors", func() {
-					mockRIPManager.EXPECT().
-						ListReservedIPs(gomock.All()).
-						Return(nil, constant.ErrUnknown).
-						Times(1)
+					patches := gomonkey.ApplyMethodReturn(fakeClient, "List", constant.ErrUnknown)
+					defer patches.Reset()
 
-					ipVersion := constant.IPv4
-					rIPT.Spec.IPVersion = &ipVersion
+					rIPT.Spec.IPVersion = pointer.Int64(constant.IPv4)
 					rIPT.Spec.IPs = append(rIPT.Spec.IPs, "172.18.40.1-172.18.40.2")
 
 					newRIPT := rIPT.DeepCopy()
@@ -485,50 +442,42 @@ var _ = Describe("ReservedIPWebhook", Label("reservedip_webhook_test"), func() {
 				})
 
 				It("exists invalid ReservedIPs in the cluster", func() {
-					existRIPT := rIPT.DeepCopy()
-					existRIPT.Name = "exist-reservedip"
-					ipVersion := constant.IPv4
-					existRIPT.Spec.IPVersion = &ipVersion
+					existRIPT.Spec.IPVersion = pointer.Int64(constant.IPv4)
 					existRIPT.Spec.IPs = append(existRIPT.Spec.IPs, constant.InvalidIPRange)
-					rIPListT.Items = append(rIPListT.Items, *existRIPT)
 
-					mockRIPManager.EXPECT().
-						ListReservedIPs(gomock.All()).
-						Return(rIPListT, nil).
-						Times(1)
+					ctx := context.TODO()
+					err := fakeClient.Create(ctx, existRIPT)
+					Expect(err).NotTo(HaveOccurred())
 
-					rIPT.Spec.IPVersion = &ipVersion
+					rIPT.Spec.IPVersion = pointer.Int64(constant.IPv4)
 					rIPT.Spec.IPs = append(rIPT.Spec.IPs, "172.18.40.1-172.18.40.2")
+					err = fakeClient.Create(ctx, rIPT)
+					Expect(err).NotTo(HaveOccurred())
 
 					newRIPT := rIPT.DeepCopy()
 					newRIPT.Spec.IPs = append(newRIPT.Spec.IPs, "172.18.40.10")
 
-					ctx := context.TODO()
-					err := rIPWebhook.ValidateUpdate(ctx, rIPT, newRIPT)
+					err = rIPWebhook.ValidateUpdate(ctx, rIPT, newRIPT)
 					Expect(apierrors.IsInvalid(err)).To(BeTrue())
 				})
 
-				It("overlaps with the existing ReservedIP", func() {
-					existRIPT := rIPT.DeepCopy()
-					existRIPT.Name = "exist-reservedip"
-					ipVersion := constant.IPv4
-					existRIPT.Spec.IPVersion = &ipVersion
+				It("overlaps with existing ReservedIP", func() {
+					existRIPT.Spec.IPVersion = pointer.Int64(constant.IPv4)
 					existRIPT.Spec.IPs = append(existRIPT.Spec.IPs, "172.18.40.10")
 
-					rIPT.Spec.IPVersion = &ipVersion
-					rIPT.Spec.IPs = append(rIPT.Spec.IPs, "172.18.40.1-172.18.40.2")
-					rIPListT.Items = append(rIPListT.Items, *rIPT, *existRIPT)
+					ctx := context.TODO()
+					err := fakeClient.Create(ctx, existRIPT)
+					Expect(err).NotTo(HaveOccurred())
 
-					mockRIPManager.EXPECT().
-						ListReservedIPs(gomock.All()).
-						Return(rIPListT, nil).
-						Times(1)
+					rIPT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					rIPT.Spec.IPs = append(rIPT.Spec.IPs, "172.18.40.1-172.18.40.2")
+					err = fakeClient.Create(ctx, rIPT)
+					Expect(err).NotTo(HaveOccurred())
 
 					newRIPT := rIPT.DeepCopy()
 					newRIPT.Spec.IPs = append(newRIPT.Spec.IPs, "172.18.40.10")
 
-					ctx := context.TODO()
-					err := rIPWebhook.ValidateUpdate(ctx, rIPT, newRIPT)
+					err = rIPWebhook.ValidateUpdate(ctx, rIPT, newRIPT)
 					Expect(apierrors.IsInvalid(err)).To(BeTrue())
 				})
 			})
@@ -556,13 +505,7 @@ var _ = Describe("ReservedIPWebhook", Label("reservedip_webhook_test"), func() {
 			})
 
 			It("updates IPv4 ReservedIP with all fields valid", func() {
-				mockRIPManager.EXPECT().
-					ListReservedIPs(gomock.All()).
-					Return(rIPListT, nil).
-					Times(1)
-
-				ipVersion := constant.IPv4
-				rIPT.Spec.IPVersion = &ipVersion
+				rIPT.Spec.IPVersion = pointer.Int64(constant.IPv4)
 				rIPT.Spec.IPs = append(rIPT.Spec.IPs, "172.18.40.1-172.18.40.2")
 
 				newRIPT := rIPT.DeepCopy()
@@ -574,13 +517,7 @@ var _ = Describe("ReservedIPWebhook", Label("reservedip_webhook_test"), func() {
 			})
 
 			It("updates IPv6 ReservedIP with all fields valid", func() {
-				mockRIPManager.EXPECT().
-					ListReservedIPs(gomock.All()).
-					Return(rIPListT, nil).
-					Times(1)
-
-				ipVersion := constant.IPv6
-				rIPT.Spec.IPVersion = &ipVersion
+				rIPT.Spec.IPVersion = pointer.Int64(constant.IPv6)
 				rIPT.Spec.IPs = append(rIPT.Spec.IPs, "abcd:1234::1-abcd:1234::2")
 
 				newRIPT := rIPT.DeepCopy()
diff --git a/pkg/subnetmanager/subnet_manager_suite_test.go b/pkg/subnetmanager/subnet_manager_suite_test.go
new file mode 100644
index 000000000..b0e8760c9
--- /dev/null
+++ b/pkg/subnetmanager/subnet_manager_suite_test.go
@@ -0,0 +1,40 @@
+// Copyright 2022 Authors of spidernet-io
+// SPDX-License-Identifier: Apache-2.0
+
+package subnetmanager_test
+
+import (
+	"testing"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+	"k8s.io/apimachinery/pkg/runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
+
+	spiderpoolv1 "github.com/spidernet-io/spiderpool/pkg/k8s/apis/spiderpool.spidernet.io/v1"
+	"github.com/spidernet-io/spiderpool/pkg/subnetmanager"
+)
+
+var scheme *runtime.Scheme
+var fakeClient client.Client
+var subnetWebhook *subnetmanager.SubnetWebhook
+
+func TestSubnetManager(t *testing.T) {
+	RegisterFailHandler(Fail)
+	RunSpecs(t, "SubnetManager Suite", Label("subnetmanager", "unitest"))
+}
+
+var _ = BeforeSuite(func() {
+	scheme = runtime.NewScheme()
+	err := spiderpoolv1.AddToScheme(scheme)
+	Expect(err).NotTo(HaveOccurred())
+
+	fakeClient = fake.NewClientBuilder().
+		WithScheme(scheme).
+		Build()
+
+	subnetWebhook = &subnetmanager.SubnetWebhook{
+		Client: fakeClient,
+	}
+})
diff --git a/pkg/subnetmanager/subnet_webhook_test.go b/pkg/subnetmanager/subnet_webhook_test.go
new file mode 100644
index 000000000..9d8c4a90b
--- /dev/null
+++ b/pkg/subnetmanager/subnet_webhook_test.go
@@ -0,0 +1,1148 @@
+// Copyright 2022 Authors of spidernet-io
+// SPDX-License-Identifier: Apache-2.0
+
+package subnetmanager_test
+
+import (
+	"context"
+	"fmt"
+	"sync/atomic"
+
+	"github.com/agiledragon/gomonkey/v2"
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/utils/pointer"
+	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/client/config"
+	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
+	"sigs.k8s.io/controller-runtime/pkg/manager"
+
+	"github.com/spidernet-io/spiderpool/pkg/constant"
+	spiderpoolip "github.com/spidernet-io/spiderpool/pkg/ip"
+	spiderpoolv1 "github.com/spidernet-io/spiderpool/pkg/k8s/apis/spiderpool.spidernet.io/v1"
+	"github.com/spidernet-io/spiderpool/pkg/logutils"
+	"github.com/spidernet-io/spiderpool/pkg/subnetmanager"
+)
+
+var _ = Describe("SubnetWebhook", Label("subnet_webhook_test"), func() {
+	Describe("Set up SubnetWebhook", func() {
+		PIt("talks to a Kubernetes API server", func() {
+			cfg, err := config.GetConfig()
+			Expect(err).NotTo(HaveOccurred())
+			Expect(cfg).NotTo(BeNil())
+
+			mgr, err := ctrl.NewManager(cfg, manager.Options{Scheme: scheme})
+			Expect(err).NotTo(HaveOccurred())
+
+			err = subnetWebhook.SetupWebhookWithManager(mgr)
+			Expect(err).NotTo(HaveOccurred())
+		})
+	})
+
+	Describe("Test SubnetWebhook's method", func() {
+		var count uint64
+		var subnetName, existSubnetName string
+		var subnetT, existSubnetT *spiderpoolv1.SpiderSubnet
+
+		BeforeEach(func() {
+			subnetmanager.WebhookLogger = logutils.Logger.Named("Subnet-Webhook")
+			subnetWebhook.EnableIPv4 = true
+			subnetWebhook.EnableIPv6 = true
+
+			atomic.AddUint64(&count, 1)
+			subnetName = fmt.Sprintf("subnet-%v", count)
+			subnetT = &spiderpoolv1.SpiderSubnet{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       constant.SpiderSubnetKind,
+					APIVersion: fmt.Sprintf("%s/%s", constant.SpiderpoolAPIGroup, constant.SpiderpoolAPIVersionV1),
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name: subnetName,
+				},
+				Spec: spiderpoolv1.SubnetSpec{},
+			}
+
+			existSubnetName = fmt.Sprintf("z-exist-subnet-%v", count)
+			existSubnetT = &spiderpoolv1.SpiderSubnet{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       constant.SpiderSubnetKind,
+					APIVersion: fmt.Sprintf("%s/%s", constant.SpiderpoolAPIGroup, constant.SpiderpoolAPIVersionV1),
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name: existSubnetName,
+				},
+				Spec: spiderpoolv1.SubnetSpec{},
+			}
+		})
+
+		var deleteOption *client.DeleteOptions
+
+		AfterEach(func() {
+			policy := metav1.DeletePropagationForeground
+			deleteOption = &client.DeleteOptions{
+				GracePeriodSeconds: pointer.Int64(0),
+				PropagationPolicy:  &policy,
+			}
+
+			ctx := context.TODO()
+			err := fakeClient.Delete(ctx, subnetT, deleteOption)
+			Expect(client.IgnoreNotFound(err)).NotTo(HaveOccurred())
+
+			err = fakeClient.Delete(ctx, existSubnetT, deleteOption)
+			Expect(client.IgnoreNotFound(err)).NotTo(HaveOccurred())
+		})
+
+		Describe("Default", func() {
+			It("avoids modifying the terminating Subnet", func() {
+				now := metav1.Now()
+				subnetT.SetDeletionTimestamp(&now)
+
+				ctx := context.TODO()
+				err := subnetWebhook.Default(ctx, subnetT)
+				Expect(err).NotTo(HaveOccurred())
+			})
+
+			It("adds finalizer", func() {
+				subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+
+				ctx := context.TODO()
+				err := subnetWebhook.Default(ctx, subnetT)
+				Expect(err).NotTo(HaveOccurred())
+
+				contains := controllerutil.ContainsFinalizer(subnetT, constant.SpiderFinalizer)
+				Expect(contains).To(BeTrue())
+			})
+
+			It("failed to set 'spec.ipVersion' due to the invalid 'spec.subnet'", func() {
+				subnetT.Spec.Subnet = constant.InvalidCIDR
+
+				ctx := context.TODO()
+				err := subnetWebhook.Default(ctx, subnetT)
+				Expect(err).NotTo(HaveOccurred())
+				Expect(subnetT.Spec.IPVersion).To(BeNil())
+			})
+
+			It("sets 'spec.ipVersion' to 4", func() {
+				subnetT.Spec.Subnet = "172.18.40.0/24"
+
+				ctx := context.TODO()
+				err := subnetWebhook.Default(ctx, subnetT)
+				Expect(err).NotTo(HaveOccurred())
+				Expect(*subnetT.Spec.IPVersion).To(Equal(constant.IPv4))
+			})
+
+			It("sets 'spec.ipVersion' to 6", func() {
+				subnetT.Spec.Subnet = "abcd:1234::/120"
+
+				ctx := context.TODO()
+				err := subnetWebhook.Default(ctx, subnetT)
+				Expect(err).NotTo(HaveOccurred())
+				Expect(*subnetT.Spec.IPVersion).To(Equal(constant.IPv6))
+			})
+
+			It("failed to merge 'spec.ips' due to the invalid 'spec.ipVersion'", func() {
+				subnetT.Spec.IPVersion = pointer.Int64(constant.InvalidIPVersion)
+				subnetT.Spec.IPs = append(subnetT.Spec.IPs,
+					[]string{
+						"172.18.40.10",
+						"172.18.40.1-172.18.40.2",
+						"172.18.40.2-172.18.40.3",
+					}...,
+				)
+
+				ctx := context.TODO()
+				err := subnetWebhook.Default(ctx, subnetT)
+				Expect(err).NotTo(HaveOccurred())
+				Expect(subnetT.Spec.IPs).To(Equal(
+					[]string{
+						"172.18.40.10",
+						"172.18.40.1-172.18.40.2",
+						"172.18.40.2-172.18.40.3",
+					},
+				))
+			})
+
+			It("failed to merge 'spec.ips' due to the invalid 'spec.ips'", func() {
+				subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+				subnetT.Spec.IPs = append(subnetT.Spec.IPs,
+					[]string{
+						constant.InvalidIPRange,
+						"172.18.40.10",
+						"172.18.40.1-172.18.40.2",
+						"172.18.40.2-172.18.40.3",
+					}...,
+				)
+
+				ctx := context.TODO()
+				err := subnetWebhook.Default(ctx, subnetT)
+				Expect(err).NotTo(HaveOccurred())
+				Expect(subnetT.Spec.IPs).To(Equal(
+					[]string{
+						constant.InvalidIPRange,
+						"172.18.40.10",
+						"172.18.40.1-172.18.40.2",
+						"172.18.40.2-172.18.40.3",
+					},
+				))
+			})
+
+			It("merges IPv4 'spec.ips'", func() {
+				subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+				subnetT.Spec.IPs = append(subnetT.Spec.IPs,
+					[]string{
+						"172.18.40.10",
+						"172.18.40.1-172.18.40.2",
+						"172.18.40.2-172.18.40.3",
+					}...,
+				)
+
+				ctx := context.TODO()
+				err := subnetWebhook.Default(ctx, subnetT)
+				Expect(err).NotTo(HaveOccurred())
+				Expect(subnetT.Spec.IPs).To(Equal(
+					[]string{
+						"172.18.40.1-172.18.40.3",
+						"172.18.40.10",
+					},
+				))
+			})
+
+			It("merges IPv6 'spec.ips'", func() {
+				subnetT.Spec.IPVersion = pointer.Int64(constant.IPv6)
+				subnetT.Spec.IPs = append(subnetT.Spec.IPs,
+					[]string{
+						"abcd:1234::a",
+						"abcd:1234::1-abcd:1234::2",
+						"abcd:1234::2-abcd:1234::3",
+					}...,
+				)
+
+				ctx := context.TODO()
+				err := subnetWebhook.Default(ctx, subnetT)
+				Expect(err).NotTo(HaveOccurred())
+				Expect(subnetT.Spec.IPs).To(Equal(
+					[]string{
+						"abcd:1234::1-abcd:1234::3",
+						"abcd:1234::a",
+					},
+				))
+			})
+
+			It("failed to merge 'spec.excludeIPs' due to the invalid 'spec.ipVersion'", func() {
+				subnetT.Spec.IPVersion = pointer.Int64(constant.InvalidIPVersion)
+				subnetT.Spec.ExcludeIPs = append(subnetT.Spec.ExcludeIPs,
+					[]string{
+						"172.18.40.10",
+						"172.18.40.1-172.18.40.2",
+						"172.18.40.2-172.18.40.3",
+					}...,
+				)
+
+				ctx := context.TODO()
+				err := subnetWebhook.Default(ctx, subnetT)
+				Expect(err).NotTo(HaveOccurred())
+				Expect(subnetT.Spec.ExcludeIPs).To(Equal(
+					[]string{
+						"172.18.40.10",
+						"172.18.40.1-172.18.40.2",
+						"172.18.40.2-172.18.40.3",
+					},
+				))
+			})
+
+			It("failed to merge 'spec.excludeIPs' due to the invalid 'spec.ips'", func() {
+				subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+				subnetT.Spec.ExcludeIPs = append(subnetT.Spec.ExcludeIPs,
+					[]string{
+						constant.InvalidIPRange,
+						"172.18.40.10",
+						"172.18.40.1-172.18.40.2",
+						"172.18.40.2-172.18.40.3",
+					}...,
+				)
+
+				ctx := context.TODO()
+				err := subnetWebhook.Default(ctx, subnetT)
+				Expect(err).NotTo(HaveOccurred())
+				Expect(subnetT.Spec.ExcludeIPs).To(Equal(
+					[]string{
+						constant.InvalidIPRange,
+						"172.18.40.10",
+						"172.18.40.1-172.18.40.2",
+						"172.18.40.2-172.18.40.3",
+					},
+				))
+			})
+
+			It("merges IPv4 'spec.excludeIPs'", func() {
+				subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+				subnetT.Spec.ExcludeIPs = append(subnetT.Spec.ExcludeIPs,
+					[]string{
+						"172.18.40.10",
+						"172.18.40.1-172.18.40.2",
+						"172.18.40.2-172.18.40.3",
+					}...,
+				)
+
+				ctx := context.TODO()
+				err := subnetWebhook.Default(ctx, subnetT)
+				Expect(err).NotTo(HaveOccurred())
+				Expect(subnetT.Spec.ExcludeIPs).To(Equal(
+					[]string{
+						"172.18.40.1-172.18.40.3",
+						"172.18.40.10",
+					},
+				))
+			})
+
+			It("merges IPv6 'spec.excludeIPs'", func() {
+				subnetT.Spec.IPVersion = pointer.Int64(constant.IPv6)
+				subnetT.Spec.ExcludeIPs = append(subnetT.Spec.ExcludeIPs,
+					[]string{
+						"abcd:1234::a",
+						"abcd:1234::1-abcd:1234::2",
+						"abcd:1234::2-abcd:1234::3",
+					}...,
+				)
+
+				ctx := context.TODO()
+				err := subnetWebhook.Default(ctx, subnetT)
+				Expect(err).NotTo(HaveOccurred())
+				Expect(subnetT.Spec.ExcludeIPs).To(Equal(
+					[]string{
+						"abcd:1234::1-abcd:1234::3",
+						"abcd:1234::a",
+					},
+				))
+			})
+		})
+
+		Describe("ValidateCreate", func() {
+			When("Validating 'spec.ipVersion'", func() {
+				It("inputs nil 'spec.ipVersion'", func() {
+					subnetT.Spec.Subnet = "172.18.40.0/24"
+					subnetT.Spec.IPs = append(subnetT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					ctx := context.TODO()
+					err := subnetWebhook.ValidateCreate(ctx, subnetT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("inputs invalid 'spec.ipVersion'", func() {
+					subnetT.Spec.IPVersion = pointer.Int64(constant.InvalidIPVersion)
+					subnetT.Spec.Subnet = "172.18.40.0/24"
+					subnetT.Spec.IPs = append(subnetT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					ctx := context.TODO()
+					err := subnetWebhook.ValidateCreate(ctx, subnetT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("creates IPv4 Subnet but IPv4 is disbale'", func() {
+					subnetWebhook.EnableIPv4 = false
+					subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					subnetT.Spec.Subnet = "172.18.40.0/24"
+					subnetT.Spec.IPs = append(subnetT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					ctx := context.TODO()
+					err := subnetWebhook.ValidateCreate(ctx, subnetT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("creates IPv6 Subnet but IPv6 is disbale'", func() {
+					subnetWebhook.EnableIPv6 = false
+					subnetT.Spec.IPVersion = pointer.Int64(constant.IPv6)
+					subnetT.Spec.Subnet = "adbc:1234::/120"
+					subnetT.Spec.IPs = append(subnetT.Spec.IPs,
+						[]string{
+							"adbc:1234::1-adbc:1234::2",
+							"adbc:1234::a",
+						}...,
+					)
+
+					ctx := context.TODO()
+					err := subnetWebhook.ValidateCreate(ctx, subnetT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+			})
+
+			When("Validating 'spec.subnet'", func() {
+				It("inputs invalid 'spec.subnet'", func() {
+					subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					subnetT.Spec.Subnet = constant.InvalidCIDR
+					subnetT.Spec.IPs = append(subnetT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					ctx := context.TODO()
+					err := subnetWebhook.ValidateCreate(ctx, subnetT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("failed to list Subnets due to some unknown errors", func() {
+					patches := gomonkey.ApplyMethodReturn(fakeClient, "List", constant.ErrUnknown)
+					defer patches.Reset()
+
+					subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					subnetT.Spec.Subnet = "172.18.40.0/24"
+					subnetT.Spec.IPs = append(subnetT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					ctx := context.TODO()
+					err := subnetWebhook.ValidateCreate(ctx, subnetT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("creates an existing Subnet", func() {
+					subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					subnetT.Spec.Subnet = "172.18.40.0/24"
+					subnetT.Spec.IPs = append(subnetT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					ctx := context.TODO()
+					err := fakeClient.Create(ctx, subnetT)
+					Expect(err).NotTo(HaveOccurred())
+
+					err = subnetWebhook.ValidateCreate(ctx, subnetT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("failed to compare 'spec.subnet' with existing Subnet", func() {
+					existSubnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					existSubnetT.Spec.Subnet = constant.InvalidCIDR
+					existSubnetT.Spec.IPs = append(existSubnetT.Spec.IPs, "172.18.41.1-172.18.41.2")
+
+					ctx := context.TODO()
+					err := fakeClient.Create(ctx, existSubnetT)
+					Expect(err).NotTo(HaveOccurred())
+
+					subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					subnetT.Spec.Subnet = "172.18.40.0/24"
+					subnetT.Spec.IPs = append(subnetT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					err = subnetWebhook.ValidateCreate(ctx, subnetT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("overlaps with existing Subnet", func() {
+					existSubnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					existSubnetT.Spec.Subnet = "172.18.40.0/25"
+					existSubnetT.Spec.IPs = append(existSubnetT.Spec.IPs, "172.18.40.40")
+
+					ctx := context.TODO()
+					err := fakeClient.Create(ctx, existSubnetT)
+					Expect(err).NotTo(HaveOccurred())
+
+					subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					subnetT.Spec.Subnet = "172.18.40.0/24"
+					subnetT.Spec.IPs = append(subnetT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					err = subnetWebhook.ValidateCreate(ctx, subnetT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+			})
+
+			When("Validating 'spec.ips'", func() {
+				It("inputs empty 'spec.ips'", func() {
+					subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					subnetT.Spec.Subnet = "172.18.40.0/24"
+
+					ctx := context.TODO()
+					err := subnetWebhook.ValidateCreate(ctx, subnetT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("inputs invalid 'spec.ips'", func() {
+					subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					subnetT.Spec.Subnet = "172.18.40.0/24"
+					subnetT.Spec.IPs = append(subnetT.Spec.IPs, constant.InvalidIPRange)
+
+					ctx := context.TODO()
+					err := subnetWebhook.ValidateCreate(ctx, subnetT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("inputs 'spec.ips' that do not pertains to 'spec.subnet'", func() {
+					subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					subnetT.Spec.Subnet = "172.18.40.0/24"
+					subnetT.Spec.IPs = append(subnetT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.41.10",
+						}...,
+					)
+
+					ctx := context.TODO()
+					err := subnetWebhook.ValidateCreate(ctx, subnetT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+			})
+
+			When("Validating 'spec.excludeIPs'", func() {
+				It("inputs invalid 'spec.excludeIPs'", func() {
+					subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					subnetT.Spec.Subnet = "172.18.40.0/24"
+					subnetT.Spec.IPs = append(subnetT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+					subnetT.Spec.ExcludeIPs = append(subnetT.Spec.ExcludeIPs, constant.InvalidIPRange)
+
+					ctx := context.TODO()
+					err := subnetWebhook.ValidateCreate(ctx, subnetT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("inputs 'spec.excludeIPs' that do not pertains to 'spec.subnet'", func() {
+					subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					subnetT.Spec.Subnet = "172.18.40.0/24"
+					subnetT.Spec.IPs = append(subnetT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+					subnetT.Spec.ExcludeIPs = append(subnetT.Spec.ExcludeIPs, "172.18.41.10")
+
+					ctx := context.TODO()
+					err := subnetWebhook.ValidateCreate(ctx, subnetT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+			})
+
+			When("Validating 'spec.gateway'", func() {
+				It("inputs invalid 'spec.gateway'", func() {
+					subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					subnetT.Spec.Subnet = "172.18.40.0/24"
+					subnetT.Spec.IPs = append(subnetT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+					subnetT.Spec.Gateway = pointer.String(constant.InvalidIP)
+
+					ctx := context.TODO()
+					err := subnetWebhook.ValidateCreate(ctx, subnetT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("inputs 'spec.gateway' that do not pertains to 'spec.subnet'", func() {
+					subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					subnetT.Spec.Subnet = "172.18.40.0/24"
+					subnetT.Spec.IPs = append(subnetT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+					subnetT.Spec.Gateway = pointer.String("172.18.41.1")
+
+					ctx := context.TODO()
+					err := subnetWebhook.ValidateCreate(ctx, subnetT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+			})
+
+			When("Validating 'spec.routes'", func() {
+				It("inputs invalid destination", func() {
+					subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					subnetT.Spec.Subnet = "172.18.40.0/24"
+					subnetT.Spec.IPs = append(subnetT.Spec.IPs,
+						[]string{
+							"172.18.40.2-172.18.40.3",
+							"172.18.40.10",
+						}...,
+					)
+					subnetT.Spec.Routes = append(subnetT.Spec.Routes,
+						spiderpoolv1.Route{
+							Dst: constant.InvalidCIDR,
+							Gw:  "172.18.40.1",
+						},
+					)
+
+					ctx := context.TODO()
+					err := subnetWebhook.ValidateCreate(ctx, subnetT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("inputs invalid gateway", func() {
+					subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					subnetT.Spec.Subnet = "172.18.40.0/24"
+					subnetT.Spec.IPs = append(subnetT.Spec.IPs,
+						[]string{
+							"172.18.40.2-172.18.40.3",
+							"172.18.40.10",
+						}...,
+					)
+					subnetT.Spec.Routes = append(subnetT.Spec.Routes,
+						spiderpoolv1.Route{
+							Dst: "192.168.40.0/24",
+							Gw:  constant.InvalidIP,
+						},
+					)
+
+					ctx := context.TODO()
+					err := subnetWebhook.ValidateCreate(ctx, subnetT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("inputs gateway that do not pertains to 'spec.subnet'", func() {
+					subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					subnetT.Spec.Subnet = "172.18.40.0/24"
+					subnetT.Spec.IPs = append(subnetT.Spec.IPs,
+						[]string{
+							"172.18.40.2-172.18.40.3",
+							"172.18.40.10",
+						}...,
+					)
+					subnetT.Spec.Routes = append(subnetT.Spec.Routes,
+						spiderpoolv1.Route{
+							Dst: "192.168.40.0/24",
+							Gw:  "172.18.41.1",
+						},
+					)
+
+					ctx := context.TODO()
+					err := subnetWebhook.ValidateCreate(ctx, subnetT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+			})
+
+			It("creates IPv4 Subnet with all fields valid", func() {
+				subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+				subnetT.Spec.Subnet = "172.18.40.0/24"
+				subnetT.Spec.IPs = append(subnetT.Spec.IPs,
+					[]string{
+						"172.18.40.2-172.18.40.3",
+						"172.18.40.10",
+					}...,
+				)
+				subnetT.Spec.ExcludeIPs = append(subnetT.Spec.ExcludeIPs, "172.18.40.10")
+				subnetT.Spec.Gateway = pointer.String("172.18.40.1")
+				subnetT.Spec.Vlan = pointer.Int64(0)
+				subnetT.Spec.Routes = append(subnetT.Spec.Routes,
+					spiderpoolv1.Route{
+						Dst: "192.168.40.0/24",
+						Gw:  "172.18.40.40",
+					},
+				)
+
+				ctx := context.TODO()
+				err := subnetWebhook.ValidateCreate(ctx, subnetT)
+				Expect(err).NotTo(HaveOccurred())
+			})
+
+			It("creates IPv6 Subnet with all fields valid", func() {
+				subnetT.Spec.IPVersion = pointer.Int64(constant.IPv6)
+				subnetT.Spec.Subnet = "abcd:1234::/120"
+				subnetT.Spec.IPs = append(subnetT.Spec.IPs,
+					[]string{
+						"abcd:1234::2-abcd:1234::3",
+						"abcd:1234::a",
+					}...,
+				)
+				subnetT.Spec.ExcludeIPs = append(subnetT.Spec.ExcludeIPs, "abcd:1234::a")
+				subnetT.Spec.Gateway = pointer.String("abcd:1234::1")
+				subnetT.Spec.Vlan = pointer.Int64(0)
+				subnetT.Spec.Routes = append(subnetT.Spec.Routes,
+					spiderpoolv1.Route{
+						Dst: "fd00:40::/120",
+						Gw:  "abcd:1234::28",
+					},
+				)
+
+				ctx := context.TODO()
+				err := subnetWebhook.ValidateCreate(ctx, subnetT)
+				Expect(err).NotTo(HaveOccurred())
+			})
+		})
+
+		Describe("ValidateUpdate", func() {
+			When("Validating 'spec.ipVersion'", func() {
+				It("updates 'spec.ipVersion' to nil", func() {
+					subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					subnetT.Spec.Subnet = "172.18.40.0/24"
+					subnetT.Spec.IPs = append(subnetT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					newSubnetT := subnetT.DeepCopy()
+					newSubnetT.Spec.IPVersion = nil
+
+					ctx := context.TODO()
+					err := subnetWebhook.ValidateUpdate(ctx, subnetT, newSubnetT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("changes 'spec.ipVersion'", func() {
+					subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					subnetT.Spec.Subnet = "172.18.40.0/24"
+					subnetT.Spec.IPs = append(subnetT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					newSubnetT := subnetT.DeepCopy()
+					newSubnetT.Spec.IPVersion = pointer.Int64(constant.IPv6)
+
+					ctx := context.TODO()
+					err := subnetWebhook.ValidateUpdate(ctx, subnetT, newSubnetT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("updates IPv4 Subnet but IPv4 is disbale'", func() {
+					subnetWebhook.EnableIPv4 = false
+					subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					subnetT.Spec.Subnet = "172.18.40.0/24"
+					subnetT.Spec.IPs = append(subnetT.Spec.IPs, "172.18.40.1-172.18.40.2")
+
+					newSubnetT := subnetT.DeepCopy()
+					newSubnetT.Spec.IPs = append(newSubnetT.Spec.IPs, "172.18.40.10")
+
+					ctx := context.TODO()
+					err := subnetWebhook.ValidateUpdate(ctx, subnetT, newSubnetT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("updates IPv6 Subnet but IPv6 is disbale'", func() {
+					subnetWebhook.EnableIPv6 = false
+					subnetT.Spec.IPVersion = pointer.Int64(constant.IPv6)
+					subnetT.Spec.Subnet = "adbc:1234::/120"
+					subnetT.Spec.IPs = append(subnetT.Spec.IPs, "adbc:1234::1-adbc:1234::2")
+
+					newSubnetT := subnetT.DeepCopy()
+					newSubnetT.Spec.IPs = append(newSubnetT.Spec.IPs, "adbc:1234::a")
+
+					ctx := context.TODO()
+					err := subnetWebhook.ValidateUpdate(ctx, subnetT, newSubnetT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+			})
+
+			When("Validating 'spec.subnet'", func() {
+				It("changes 'spec.subnet'", func() {
+					subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					subnetT.Spec.Subnet = "172.18.40.0/24"
+					subnetT.Spec.IPs = append(subnetT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					newSubnetT := subnetT.DeepCopy()
+					newSubnetT.Spec.Subnet = "172.18.40.0/25"
+
+					ctx := context.TODO()
+					err := subnetWebhook.ValidateUpdate(ctx, subnetT, newSubnetT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+			})
+
+			When("Validating 'spec.ips'", func() {
+				It("removes all 'spec.ips'", func() {
+					subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					subnetT.Spec.Subnet = "172.18.40.0/24"
+					subnetT.Spec.IPs = append(subnetT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					newSubnetT := subnetT.DeepCopy()
+					newSubnetT.Spec.IPs = nil
+
+					ctx := context.TODO()
+					err := subnetWebhook.ValidateUpdate(ctx, subnetT, newSubnetT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("appends invalid IP range to 'spec.ips'", func() {
+					subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					subnetT.Spec.Subnet = "172.18.40.0/24"
+					subnetT.Spec.IPs = append(subnetT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					newSubnetT := subnetT.DeepCopy()
+					newSubnetT.Spec.IPs = append(newSubnetT.Spec.IPs, constant.InvalidIPRange)
+
+					ctx := context.TODO()
+					err := subnetWebhook.ValidateUpdate(ctx, subnetT, newSubnetT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("appends IP range that do not pertains to 'spec.subnet' to 'spec.ips'", func() {
+					subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					subnetT.Spec.Subnet = "172.18.40.0/24"
+					subnetT.Spec.IPs = append(subnetT.Spec.IPs, "172.18.40.1-172.18.40.2")
+
+					newSubnetT := subnetT.DeepCopy()
+					newSubnetT.Spec.IPs = append(newSubnetT.Spec.IPs, "172.18.41.10")
+
+					ctx := context.TODO()
+					err := subnetWebhook.ValidateUpdate(ctx, subnetT, newSubnetT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+			})
+
+			When("Validating 'spec.excludeIPs'", func() {
+				It("appends invalid IP range to 'spec.excludeIPs'", func() {
+					subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					subnetT.Spec.Subnet = "172.18.40.0/24"
+					subnetT.Spec.IPs = append(subnetT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					newSubnetT := subnetT.DeepCopy()
+					newSubnetT.Spec.ExcludeIPs = append(newSubnetT.Spec.ExcludeIPs, constant.InvalidIPRange)
+
+					ctx := context.TODO()
+					err := subnetWebhook.ValidateUpdate(ctx, subnetT, newSubnetT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("appends IP range that do not pertains to 'spec.subnet' to 'spec.excludeIPs'", func() {
+					subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					subnetT.Spec.Subnet = "172.18.40.0/24"
+					subnetT.Spec.IPs = append(subnetT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					newSubnetT := subnetT.DeepCopy()
+					newSubnetT.Spec.ExcludeIPs = append(newSubnetT.Spec.ExcludeIPs, "172.18.41.10")
+
+					ctx := context.TODO()
+					err := subnetWebhook.ValidateUpdate(ctx, subnetT, newSubnetT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+			})
+
+			When("Validating 'spec.gateway'", func() {
+				It("updates 'spec.gateway' to invalid gateway", func() {
+					subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					subnetT.Spec.Subnet = "172.18.40.0/24"
+					subnetT.Spec.IPs = append(subnetT.Spec.IPs,
+						[]string{
+							"172.18.40.2-172.18.40.3",
+							"172.18.40.10",
+						}...,
+					)
+					subnetT.Spec.Gateway = pointer.String("172.18.40.1")
+
+					newSubnetT := subnetT.DeepCopy()
+					newSubnetT.Spec.Gateway = pointer.String(constant.InvalidIP)
+
+					ctx := context.TODO()
+					err := subnetWebhook.ValidateUpdate(ctx, subnetT, newSubnetT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("updates 'spec.gateway' to a gateway that do not pertains to 'spec.subnet'", func() {
+					subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					subnetT.Spec.Subnet = "172.18.40.0/24"
+					subnetT.Spec.IPs = append(subnetT.Spec.IPs,
+						[]string{
+							"172.18.40.2-172.18.40.3",
+							"172.18.40.10",
+						}...,
+					)
+					subnetT.Spec.Gateway = pointer.String("172.18.40.1")
+
+					newSubnetT := subnetT.DeepCopy()
+					newSubnetT.Spec.Gateway = pointer.String("172.18.41.1")
+
+					ctx := context.TODO()
+					err := subnetWebhook.ValidateUpdate(ctx, subnetT, newSubnetT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+			})
+
+			When("Validating 'spec.routes'", func() {
+				It("appends route with invalid destination", func() {
+					subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					subnetT.Spec.Subnet = "172.18.40.0/24"
+					subnetT.Spec.IPs = append(subnetT.Spec.IPs,
+						[]string{
+							"172.18.40.2-172.18.40.3",
+							"172.18.40.10",
+						}...,
+					)
+
+					newSubnetT := subnetT.DeepCopy()
+					newSubnetT.Spec.Routes = append(newSubnetT.Spec.Routes,
+						spiderpoolv1.Route{
+							Dst: constant.InvalidCIDR,
+							Gw:  "172.18.40.1",
+						},
+					)
+
+					ctx := context.TODO()
+					err := subnetWebhook.ValidateUpdate(ctx, subnetT, newSubnetT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("appends route with invalid gateway", func() {
+					subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					subnetT.Spec.Subnet = "172.18.40.0/24"
+					subnetT.Spec.IPs = append(subnetT.Spec.IPs,
+						[]string{
+							"172.18.40.2-172.18.40.3",
+							"172.18.40.10",
+						}...,
+					)
+
+					newSubnetT := subnetT.DeepCopy()
+					newSubnetT.Spec.Routes = append(newSubnetT.Spec.Routes,
+						spiderpoolv1.Route{
+							Dst: "192.168.40.0/24",
+							Gw:  constant.InvalidIP,
+						},
+					)
+
+					ctx := context.TODO()
+					err := subnetWebhook.ValidateUpdate(ctx, subnetT, newSubnetT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("appends route whose gateway does not pertains to 'spec.subnet'", func() {
+					subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					subnetT.Spec.Subnet = "172.18.40.0/24"
+					subnetT.Spec.IPs = append(subnetT.Spec.IPs,
+						[]string{
+							"172.18.40.2-172.18.40.3",
+							"172.18.40.10",
+						}...,
+					)
+
+					newSubnetT := subnetT.DeepCopy()
+					newSubnetT.Spec.Routes = append(newSubnetT.Spec.Routes,
+						spiderpoolv1.Route{
+							Dst: "192.168.40.0/24",
+							Gw:  "172.18.41.1",
+						},
+					)
+
+					ctx := context.TODO()
+					err := subnetWebhook.ValidateUpdate(ctx, subnetT, newSubnetT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+			})
+
+			When("Validating the pre-allocated IP addresses", func() {
+				It("failed to assemble total IP addresses due to some unknown errors", func() {
+					patches := gomonkey.ApplyFuncReturn(spiderpoolip.AssembleTotalIPs, nil, constant.ErrUnknown)
+					defer patches.Reset()
+
+					subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					subnetT.Spec.Subnet = "172.18.40.0/24"
+					subnetT.Spec.IPs = append(subnetT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					subnetT.Status.ControlledIPPools = spiderpoolv1.PoolIPPreAllocations{
+						"pool": spiderpoolv1.PoolIPPreAllocation{
+							IPs: []string{
+								"172.18.40.10",
+							},
+						},
+					}
+
+					newSubnetT := subnetT.DeepCopy()
+					newSubnetT.Spec.IPs = newSubnetT.Spec.IPs[:1]
+
+					ctx := context.TODO()
+					err := subnetWebhook.ValidateUpdate(ctx, subnetT, newSubnetT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("has invalid 'status.controlledIPPools'", func() {
+					subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					subnetT.Spec.Subnet = "172.18.40.0/24"
+					subnetT.Spec.IPs = append(subnetT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					subnetT.Status.ControlledIPPools = spiderpoolv1.PoolIPPreAllocations{
+						"pool": spiderpoolv1.PoolIPPreAllocation{IPs: constant.InvalidIPRanges},
+					}
+
+					newSubnetT := subnetT.DeepCopy()
+					newSubnetT.Spec.IPs = newSubnetT.Spec.IPs[:1]
+
+					ctx := context.TODO()
+					err := subnetWebhook.ValidateUpdate(ctx, subnetT, newSubnetT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("removes IP range that is being used by IPPool", func() {
+					subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					subnetT.Spec.Subnet = "172.18.40.0/24"
+					subnetT.Spec.IPs = append(subnetT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					subnetT.Status.ControlledIPPools = spiderpoolv1.PoolIPPreAllocations{
+						"pool": spiderpoolv1.PoolIPPreAllocation{
+							IPs: []string{
+								"172.18.40.10",
+							},
+						},
+					}
+
+					newSubnetT := subnetT.DeepCopy()
+					newSubnetT.Spec.IPs = newSubnetT.Spec.IPs[:1]
+
+					ctx := context.TODO()
+					err := subnetWebhook.ValidateUpdate(ctx, subnetT, newSubnetT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+			})
+
+			It("deletes Subnet", func() {
+				controllerutil.AddFinalizer(subnetT, constant.SpiderFinalizer)
+				now := metav1.Now()
+				subnetT.SetDeletionTimestamp(&now)
+				subnetT.SetDeletionGracePeriodSeconds(pointer.Int64(0))
+
+				newSubnetT := subnetT.DeepCopy()
+				controllerutil.RemoveFinalizer(newSubnetT, constant.SpiderFinalizer)
+
+				ctx := context.TODO()
+				err := subnetWebhook.ValidateUpdate(ctx, subnetT, newSubnetT)
+				Expect(err).NotTo(HaveOccurred())
+			})
+
+			It("updates terminating Subnet", func() {
+				controllerutil.AddFinalizer(subnetT, constant.SpiderFinalizer)
+				now := metav1.Now()
+				subnetT.SetDeletionTimestamp(&now)
+				subnetT.SetDeletionGracePeriodSeconds(pointer.Int64(30))
+
+				newSubnetT := subnetT.DeepCopy()
+
+				ctx := context.TODO()
+				err := subnetWebhook.ValidateUpdate(ctx, subnetT, newSubnetT)
+				Expect(apierrors.IsForbidden(err)).To(BeTrue())
+			})
+
+			It("updates IPv4 Subnet with all fields valid", func() {
+				subnetT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+				subnetT.Spec.Subnet = "172.18.40.0/24"
+				subnetT.Spec.IPs = append(subnetT.Spec.IPs, "172.18.40.2-172.18.40.3")
+				subnetT.Spec.Vlan = pointer.Int64(0)
+
+				newSubnetT := subnetT.DeepCopy()
+				newSubnetT.Spec.IPs = append(newSubnetT.Spec.IPs, "172.18.40.10")
+				newSubnetT.Spec.ExcludeIPs = append(newSubnetT.Spec.ExcludeIPs, "172.18.40.10")
+				newSubnetT.Spec.Gateway = pointer.String("172.18.40.1")
+				newSubnetT.Spec.Routes = append(newSubnetT.Spec.Routes,
+					spiderpoolv1.Route{
+						Dst: "192.168.40.0/24",
+						Gw:  "172.18.40.40",
+					},
+				)
+
+				ctx := context.TODO()
+				err := subnetWebhook.ValidateUpdate(ctx, subnetT, newSubnetT)
+				Expect(err).NotTo(HaveOccurred())
+			})
+
+			It("updates IPv6 Subnet with all fields valid", func() {
+				subnetT.Spec.IPVersion = pointer.Int64(constant.IPv6)
+				subnetT.Spec.Subnet = "abcd:1234::/120"
+				subnetT.Spec.IPs = append(subnetT.Spec.IPs, "abcd:1234::2-abcd:1234::3")
+				subnetT.Spec.Vlan = pointer.Int64(0)
+
+				newSubnetT := subnetT.DeepCopy()
+				newSubnetT.Spec.IPs = append(newSubnetT.Spec.IPs, "abcd:1234::a")
+				newSubnetT.Spec.ExcludeIPs = append(newSubnetT.Spec.ExcludeIPs, "abcd:1234::a")
+				newSubnetT.Spec.Gateway = pointer.String("abcd:1234::1")
+				newSubnetT.Spec.Routes = append(newSubnetT.Spec.Routes,
+					spiderpoolv1.Route{
+						Dst: "fd00:40::/120",
+						Gw:  "abcd:1234::28",
+					},
+				)
+
+				ctx := context.TODO()
+				err := subnetWebhook.ValidateUpdate(ctx, subnetT, newSubnetT)
+				Expect(err).NotTo(HaveOccurred())
+			})
+		})
+
+		Describe("ValidateDelete", func() {
+			It("passes", func() {
+				ctx := context.TODO()
+				err := subnetWebhook.ValidateDelete(ctx, subnetT)
+				Expect(err).NotTo(HaveOccurred())
+			})
+		})
+	})
+})
diff --git a/pkg/workloadendpointmanager/workloadendpoint_webhook_test.go b/pkg/workloadendpointmanager/workloadendpoint_webhook_test.go
index 7994e2239..0d0046049 100644
--- a/pkg/workloadendpointmanager/workloadendpoint_webhook_test.go
+++ b/pkg/workloadendpointmanager/workloadendpoint_webhook_test.go
@@ -65,7 +65,7 @@ var _ = Describe("WorkloadEndpointWebhook", Label("workloadendpoint_webhook_test
 				Expect(err).NotTo(HaveOccurred())
 			})
 
-			It("sets finalizer", func() {
+			It("adds finalizer", func() {
 				ctx := context.TODO()
 				err := endpointWebhook.Default(ctx, endpointT)
 				Expect(err).NotTo(HaveOccurred())