Repository: spidernet-io/spiderpool
Pull request #1258 (2023-01-05 08:38:52)

test: Add webhook unit tests for pkg ippoolmanager
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=1258)

[![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=1258&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=1258&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1258&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=1258&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=1258&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1258&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=1258&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=spidernet-io_spiderpool&pullRequest=1258&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=spidernet-io_spiderpool&pullRequest=1258&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=1258&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=1258&resolved=false&types=CODE_SMELL) [5 Code Smells](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1258&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=1258) No Coverage information  
[![4.8%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/5-16px.png '4.8%')](https://sonarcloud.io/component_measures?id=spidernet-io_spiderpool&pullRequest=1258&metric=new_duplicated_lines_density&view=list) [4.8% Duplication](https://sonarcloud.io/component_measures?id=spidernet-io_spiderpool&pullRequest=1258&metric=new_duplicated_lines_density&view=list)


codecov[bot]: # [Codecov](https://codecov.io/gh/spidernet-io/spiderpool/pull/1258?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spidernet-io) Report
> Merging [#1258](https://codecov.io/gh/spidernet-io/spiderpool/pull/1258?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spidernet-io) (7aaf3ca) 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 **not change** coverage.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/spidernet-io/spiderpool/pull/1258/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/1258?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    #1258   +/-   ##
=======================================
  Coverage   87.88%   87.88%           
=======================================
  Files          32       32           
  Lines        1873     1873           
=======================================
  Hits         1646     1646           
  Misses        200      200           
  Partials       27       27           
```

| Flag | Coverage Δ | |
|---|---|---|
| unittests | `87.88% <ø> (ø)` | |

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.


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=1258)

[![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=1258&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=1258&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1258&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=1258&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=1258&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1258&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=1258&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=1258&resolved=false&types=SECURITY_HOTSPOT) [9 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=spidernet-io_spiderpool&pullRequest=1258&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=1258&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=1258&resolved=false&types=CODE_SMELL) [8 Code Smells](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1258&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=1258) No Coverage information  
[![5.5%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/10-16px.png '5.5%')](https://sonarcloud.io/component_measures?id=spidernet-io_spiderpool&pullRequest=1258&metric=new_duplicated_lines_density&view=list) [5.5% Duplication](https://sonarcloud.io/component_measures?id=spidernet-io_spiderpool&pullRequest=1258&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=1258)

[![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=1258&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=1258&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1258&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=1258&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=1258&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1258&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=1258&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=1258&resolved=false&types=SECURITY_HOTSPOT) [92 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=spidernet-io_spiderpool&pullRequest=1258&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=1258&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=1258&resolved=false&types=CODE_SMELL) [12 Code Smells](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1258&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=1258) No Coverage information  
[![8.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/10-16px.png '8.0%')](https://sonarcloud.io/component_measures?id=spidernet-io_spiderpool&pullRequest=1258&metric=new_duplicated_lines_density&view=list) [8.0% Duplication](https://sonarcloud.io/component_measures?id=spidernet-io_spiderpool&pullRequest=1258&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=1258)

[![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=1258&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=1258&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1258&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=1258&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=1258&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1258&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=1258&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=1258&resolved=false&types=SECURITY_HOTSPOT) [196 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=spidernet-io_spiderpool&pullRequest=1258&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=1258&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=1258&resolved=false&types=CODE_SMELL) [27 Code Smells](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1258&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=1258) No Coverage information  
[![12.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/20-16px.png '12.0%')](https://sonarcloud.io/component_measures?id=spidernet-io_spiderpool&pullRequest=1258&metric=new_duplicated_lines_density&view=list) [12.0% Duplication](https://sonarcloud.io/component_measures?id=spidernet-io_spiderpool&pullRequest=1258&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=1258)

[![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=1258&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=1258&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1258&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=1258&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=1258&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1258&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=1258&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=1258&resolved=false&types=SECURITY_HOTSPOT) [196 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=spidernet-io_spiderpool&pullRequest=1258&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=1258&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=1258&resolved=false&types=CODE_SMELL) [27 Code Smells](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1258&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=1258) No Coverage information  
[![12.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/20-16px.png '12.0%')](https://sonarcloud.io/component_measures?id=spidernet-io_spiderpool&pullRequest=1258&metric=new_duplicated_lines_density&view=list) [12.0% Duplication](https://sonarcloud.io/component_measures?id=spidernet-io_spiderpool&pullRequest=1258&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=1258)

[![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=1258&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=1258&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1258&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=1258&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=1258&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1258&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=1258&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=1258&resolved=false&types=SECURITY_HOTSPOT) [196 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=spidernet-io_spiderpool&pullRequest=1258&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=1258&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=1258&resolved=false&types=CODE_SMELL) [27 Code Smells](https://sonarcloud.io/project/issues?id=spidernet-io_spiderpool&pullRequest=1258&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=1258) No Coverage information  
[![12.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/20-16px.png '12.0%')](https://sonarcloud.io/component_measures?id=spidernet-io_spiderpool&pullRequest=1258&metric=new_duplicated_lines_density&view=list) [12.0% Duplication](https://sonarcloud.io/component_measures?id=spidernet-io_spiderpool&pullRequest=1258&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 5a7032431..678b1939c 100644
--- a/cmd/spiderpool-controller/cmd/daemon.go
+++ b/cmd/spiderpool-controller/cmd/daemon.go
@@ -322,8 +322,13 @@ func initControllerServiceManagers(ctx context.Context) {
 
 	// set up spiderpool controller IPPool webhook
 	logger.Debug("Begin to set up IPPool webhook")
-	err = controllerContext.IPPoolManager.SetupWebhook()
-	if err != nil {
+	if err := (&ippoolmanager.IPPoolWebhook{
+		Client:             controllerContext.CRDManager.GetClient(),
+		Scheme:             controllerContext.CRDManager.GetScheme(),
+		EnableIPv4:         controllerContext.Cfg.EnableIPv4,
+		EnableIPv6:         controllerContext.Cfg.EnableIPv6,
+		EnableSpiderSubnet: controllerContext.Cfg.EnableSpiderSubnet,
+	}).SetupWebhookWithManager(controllerContext.CRDManager); err != nil {
 		logger.Fatal(err.Error())
 	}
 
diff --git a/pkg/ippoolmanager/ippool_manager.go b/pkg/ippoolmanager/ippool_manager.go
index f8f122389..2a88d362f 100644
--- a/pkg/ippoolmanager/ippool_manager.go
+++ b/pkg/ippoolmanager/ippool_manager.go
@@ -109,12 +109,12 @@ func (im *ipPoolManager) GetIPPoolByName(ctx context.Context, poolName string) (
 }
 
 func (im *ipPoolManager) ListIPPools(ctx context.Context, opts ...client.ListOption) (*spiderpoolv1.SpiderIPPoolList, error) {
-	ipPoolList := &spiderpoolv1.SpiderIPPoolList{}
-	if err := im.client.List(ctx, ipPoolList, opts...); err != nil {
+	var ipPoolList spiderpoolv1.SpiderIPPoolList
+	if err := im.client.List(ctx, &ipPoolList, opts...); err != nil {
 		return nil, err
 	}
 
-	return ipPoolList, nil
+	return &ipPoolList, nil
 }
 
 func (im *ipPoolManager) AllocateIP(ctx context.Context, poolName, containerID, nic string, pod *corev1.Pod) (*models.IPConfig, *spiderpoolv1.SpiderIPPool, error) {
diff --git a/pkg/ippoolmanager/ippool_mutate.go b/pkg/ippoolmanager/ippool_mutate.go
new file mode 100644
index 000000000..5f90c3b6d
--- /dev/null
+++ b/pkg/ippoolmanager/ippool_mutate.go
@@ -0,0 +1,115 @@
+// Copyright 2022 Authors of spidernet-io
+// SPDX-License-Identifier: Apache-2.0
+
+package ippoolmanager
+
+import (
+	"context"
+	"errors"
+	"fmt"
+
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	ctrl "sigs.k8s.io/controller-runtime"
+	"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 (iw *IPPoolWebhook) mutateIPPool(ctx context.Context, ipPool *spiderpoolv1.SpiderIPPool) error {
+	logger := logutils.FromContext(ctx)
+	logger.Info("Start to mutate IPPool")
+
+	if ipPool.DeletionTimestamp != nil {
+		logger.Info("Terminating IPPool, noting to mutate")
+		return nil
+	}
+
+	if iw.EnableSpiderSubnet {
+		if err := iw.setControllerSubnet(ctx, ipPool); err != nil {
+			return apierrors.NewInternalError(fmt.Errorf("failed to set the reference of the controller Subnet: %v", err))
+		}
+	}
+
+	if !controllerutil.ContainsFinalizer(ipPool, constant.SpiderFinalizer) {
+		controllerutil.AddFinalizer(ipPool, constant.SpiderFinalizer)
+		logger.Sugar().Infof("Add finalizer %s", constant.SpiderFinalizer)
+	}
+
+	if ipPool.Spec.IPVersion == nil {
+		var version types.IPVersion
+		if spiderpoolip.IsIPv4CIDR(ipPool.Spec.Subnet) {
+			version = constant.IPv4
+		} else if spiderpoolip.IsIPv6CIDR(ipPool.Spec.Subnet) {
+			version = constant.IPv6
+		} else {
+			return errors.New("invalid 'spec.ipVersion', noting to mutate")
+		}
+
+		ipPool.Spec.IPVersion = new(types.IPVersion)
+		*ipPool.Spec.IPVersion = version
+		logger.Sugar().Infof("Set 'spec.ipVersion' to %d", version)
+	}
+
+	if len(ipPool.Spec.IPs) > 1 {
+		mergedIPs, err := spiderpoolip.MergeIPRanges(*ipPool.Spec.IPVersion, ipPool.Spec.IPs)
+		if err != nil {
+			return fmt.Errorf("failed to merge 'spec.ips': %v", err)
+		}
+
+		ipPool.Spec.IPs = mergedIPs
+		logger.Sugar().Debugf("Merge 'spec.ips':\n%v\n\nto:\n\n%v", ipPool.Spec.IPs, mergedIPs)
+	}
+
+	if len(ipPool.Spec.ExcludeIPs) > 1 {
+		mergedExcludeIPs, err := spiderpoolip.MergeIPRanges(*ipPool.Spec.IPVersion, ipPool.Spec.ExcludeIPs)
+		if err != nil {
+			return fmt.Errorf("failed to merge 'spec.excludeIPs': %v", err)
+		}
+
+		ipPool.Spec.ExcludeIPs = mergedExcludeIPs
+		logger.Sugar().Debugf("Merge 'spec.excludeIPs':\n%v\n\nto:\n\n%v", ipPool.Spec.ExcludeIPs, mergedExcludeIPs)
+	}
+
+	return nil
+}
+
+func (iw *IPPoolWebhook) setControllerSubnet(ctx context.Context, ipPool *spiderpoolv1.SpiderIPPool) error {
+	logger := logutils.FromContext(ctx)
+
+	owner := metav1.GetControllerOf(ipPool)
+	if v, ok := ipPool.Labels[constant.LabelIPPoolOwnerSpiderSubnet]; ok && owner != nil && v == owner.Name {
+		return nil
+	}
+
+	var subnetList spiderpoolv1.SpiderSubnetList
+	if err := iw.Client.List(ctx, &subnetList); err != nil {
+		return fmt.Errorf("failed to list Subnets: %v", err)
+	}
+
+	for _, subnet := range subnetList.Items {
+		if subnet.Spec.Subnet == ipPool.Spec.Subnet {
+			if !metav1.IsControlledBy(ipPool, &subnet) {
+				if err := ctrl.SetControllerReference(&subnet, ipPool, iw.Scheme); err != nil {
+					return fmt.Errorf("failed to set owner reference: %v", err)
+				}
+				logger.Sugar().Infof("Set owner reference as Subnet %s", subnet.Name)
+			}
+
+			if ipPool.Labels == nil {
+				ipPool.Labels = make(map[string]string)
+			}
+			if v, ok := ipPool.Labels[constant.LabelIPPoolOwnerSpiderSubnet]; !ok || v != subnet.Name {
+				ipPool.Labels[constant.LabelIPPoolOwnerSpiderSubnet] = subnet.Name
+				logger.Sugar().Infof("Set label %s: %s", constant.LabelIPPoolOwnerSpiderSubnet, subnet.Name)
+			}
+			break
+		}
+	}
+
+	return nil
+}
diff --git a/pkg/ippoolmanager/ippool_subnet_validate.go b/pkg/ippoolmanager/ippool_subnet_validate.go
index f8993230d..424bf18a5 100644
--- a/pkg/ippoolmanager/ippool_subnet_validate.go
+++ b/pkg/ippoolmanager/ippool_subnet_validate.go
@@ -7,82 +7,87 @@ import (
 	"context"
 	"fmt"
 
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	apitypes "k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/util/validation/field"
 
 	spiderpoolip "github.com/spidernet-io/spiderpool/pkg/ip"
 	spiderpoolv1 "github.com/spidernet-io/spiderpool/pkg/k8s/apis/spiderpool.spidernet.io/v1"
 )
 
-func (im *ipPoolManager) validateCreateIPPoolAndUpdateSubnetFreeIPs(ctx context.Context, ipPool *spiderpoolv1.SpiderIPPool) field.ErrorList {
-	if errs := im.validateCreateIPPool(ctx, ipPool); len(errs) != 0 {
+func (iw *IPPoolWebhook) validateCreateIPPoolWhileEnableSpiderSubnet(ctx context.Context, ipPool *spiderpoolv1.SpiderIPPool) field.ErrorList {
+	if errs := iw.validateCreateIPPool(ctx, ipPool); len(errs) != 0 {
 		return errs
 	}
-	if !im.config.EnableSpiderSubnet {
-		return nil
-	}
 
-	subnet, err := im.validateSubnetControllerExist(ctx, ipPool)
-	if err != nil {
-		return field.ErrorList{err}
-	}
-	if err := validateSubnetTotalIPsContainsIPPoolTotalIPs(subnet, ipPool); err != nil {
-		return field.ErrorList{err}
+	if iw.EnableSpiderSubnet {
+		subnet, err := iw.validateSubnetControllerExist(ctx, ipPool)
+		if err != nil {
+			return field.ErrorList{err}
+		}
+		if err := validateSubnetTotalIPsContainsIPPoolTotalIPs(subnet, ipPool); err != nil {
+			return field.ErrorList{err}
+		}
 	}
 
 	return nil
 }
 
-func (im *ipPoolManager) validateUpdateIPPoolAndUpdateSubnetFreeIPs(ctx context.Context, oldIPPool, newIPPool *spiderpoolv1.SpiderIPPool) field.ErrorList {
-	if errs := im.validateUpdateIPPool(ctx, oldIPPool, newIPPool); len(errs) != 0 {
+func (iw *IPPoolWebhook) validateUpdateIPPoolWhileEnableSpiderSubnet(ctx context.Context, oldIPPool, newIPPool *spiderpoolv1.SpiderIPPool) field.ErrorList {
+	if errs := iw.validateUpdateIPPool(ctx, oldIPPool, newIPPool); len(errs) != 0 {
 		return errs
 	}
-	if !im.config.EnableSpiderSubnet {
-		return nil
-	}
 
-	subnet, err := im.validateSubnetControllerExist(ctx, newIPPool)
-	if err != nil {
-		return field.ErrorList{err}
-	}
-	if err := validateSubnetTotalIPsContainsIPPoolTotalIPs(subnet, newIPPool); err != nil {
-		return field.ErrorList{err}
+	if iw.EnableSpiderSubnet {
+		subnet, err := iw.validateSubnetControllerExist(ctx, newIPPool)
+		if err != nil {
+			return field.ErrorList{err}
+		}
+		if err := validateSubnetTotalIPsContainsIPPoolTotalIPs(subnet, newIPPool); err != nil {
+			return field.ErrorList{err}
+		}
+
 	}
 
 	return nil
 }
 
-func (im *ipPoolManager) validateSubnetControllerExist(ctx context.Context, ipPool *spiderpoolv1.SpiderIPPool) (*spiderpoolv1.SpiderSubnet, *field.Error) {
-	subnetList, err := im.subnetManager.ListSubnets(ctx)
-	if err != nil {
-		return nil, field.InternalError(subnetField, err)
+func (iw *IPPoolWebhook) validateSubnetControllerExist(ctx context.Context, ipPool *spiderpoolv1.SpiderIPPool) (*spiderpoolv1.SpiderSubnet, *field.Error) {
+	owner := metav1.GetControllerOf(ipPool)
+	if owner == nil {
+		return nil, field.Forbidden(
+			subnetField,
+			fmt.Sprintf("orphan IPPool, must be controlled by Subnet with the same 'spec.subnet' %s", ipPool.Spec.Subnet),
+		)
 	}
 
-	for _, subnet := range subnetList.Items {
-		if subnet.Spec.Subnet == ipPool.Spec.Subnet {
-			if subnet.DeletionTimestamp != nil {
-				return nil, field.Forbidden(
-					subnetField,
-					fmt.Sprintf("cannot update IPPool that controlled by terminating Subnet %s", subnet.Name),
-				)
-			}
-			return &subnet, nil
-		}
+	var subnet spiderpoolv1.SpiderSubnet
+	if err := iw.Client.Get(ctx, apitypes.NamespacedName{Name: owner.Name}, &subnet); err != nil {
+		return nil, field.InternalError(subnetField, fmt.Errorf("failed to get controller Subnet %s: %v", owner.Name, err))
+	}
+
+	if subnet.Spec.Subnet != ipPool.Spec.Subnet {
+		return nil, field.InternalError(subnetField, fmt.Errorf("invalid controller Subnet %s with different 'spec.subnet' %s", owner.Name, subnet.Spec.Subnet))
+	}
+
+	if subnet.DeletionTimestamp != nil {
+		return nil, field.Forbidden(
+			subnetField,
+			fmt.Sprintf("cannot update IPPool that controlled by terminating Subnet %s", subnet.Name),
+		)
 	}
 
-	return nil, field.Forbidden(
-		subnetField,
-		fmt.Sprintf("orphan IPPool, must be controlled by Subnet with the same 'spec.subnet' %s", ipPool.Spec.Subnet),
-	)
+	return &subnet, nil
 }
 
 func validateSubnetTotalIPsContainsIPPoolTotalIPs(subnet *spiderpoolv1.SpiderSubnet, ipPool *spiderpoolv1.SpiderIPPool) *field.Error {
 	poolTotalIPs, err := spiderpoolip.AssembleTotalIPs(*ipPool.Spec.IPVersion, ipPool.Spec.IPs, ipPool.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 IPPool %s: %v", ipPool.Name, err))
 	}
 	subnetTotalIPs, 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))
 	}
 
 	outIPs := spiderpoolip.IPsDiffSet(poolTotalIPs, subnetTotalIPs)
diff --git a/pkg/ippoolmanager/ippool_validate.go b/pkg/ippoolmanager/ippool_validate.go
index 2874eaa96..df6624781 100644
--- a/pkg/ippoolmanager/ippool_validate.go
+++ b/pkg/ippoolmanager/ippool_validate.go
@@ -25,9 +25,17 @@ var (
 	routesField     *field.Path = field.NewPath("spec").Child("routes")
 )
 
-func (im *ipPoolManager) validateCreateIPPool(ctx context.Context, ipPool *spiderpoolv1.SpiderIPPool) field.ErrorList {
+func (iw *IPPoolWebhook) validateCreateIPPool(ctx context.Context, ipPool *spiderpoolv1.SpiderIPPool) field.ErrorList {
+	if err := iw.validateIPPoolIPVersion(ipPool.Spec.IPVersion); err != nil {
+		return field.ErrorList{err}
+	}
+
+	if err := iw.validateIPPoolSubnet(ctx, ipPool); err != nil {
+		return field.ErrorList{err}
+	}
+
 	var errs field.ErrorList
-	if err := im.validateIPPoolSpec(ctx, ipPool); err != nil {
+	if err := iw.validateIPPoolSpec(ctx, ipPool); err != nil {
 		errs = append(errs, err)
 	}
 
@@ -38,17 +46,21 @@ func (im *ipPoolManager) validateCreateIPPool(ctx context.Context, ipPool *spide
 	return errs
 }
 
-func (im *ipPoolManager) validateUpdateIPPool(ctx context.Context, oldIPPool, newIPPool *spiderpoolv1.SpiderIPPool) field.ErrorList {
+func (iw *IPPoolWebhook) validateUpdateIPPool(ctx context.Context, oldIPPool, newIPPool *spiderpoolv1.SpiderIPPool) field.ErrorList {
 	if err := validateIPPoolShouldNotBeChanged(oldIPPool, newIPPool); err != nil {
 		return field.ErrorList{err}
 	}
 
-	if err := im.validateIPPoolSpec(ctx, newIPPool); err != nil {
+	if err := iw.validateIPPoolIPVersion(newIPPool.Spec.IPVersion); err != nil {
+		return field.ErrorList{err}
+	}
+
+	if err := iw.validateIPPoolSpec(ctx, newIPPool); err != nil {
 		return field.ErrorList{err}
 	}
 
 	var errs field.ErrorList
-	if err := validateIPPoolIPInUse(newIPPool, im.config.EnableSpiderSubnet); err != nil {
+	if err := validateIPPoolIPInUse(newIPPool); err != nil {
 		errs = append(errs, err)
 	}
 
@@ -60,7 +72,8 @@ func (im *ipPoolManager) validateUpdateIPPool(ctx context.Context, oldIPPool, ne
 }
 
 func validateIPPoolShouldNotBeChanged(oldIPPool, newIPPool *spiderpoolv1.SpiderIPPool) *field.Error {
-	if *newIPPool.Spec.IPVersion != *oldIPPool.Spec.IPVersion {
+	if newIPPool.Spec.IPVersion != nil && oldIPPool.Spec.IPVersion != nil &&
+		*newIPPool.Spec.IPVersion != *oldIPPool.Spec.IPVersion {
 		return field.Forbidden(
 			ipVersionField,
 			"is not changeable",
@@ -77,14 +90,8 @@ func validateIPPoolShouldNotBeChanged(oldIPPool, newIPPool *spiderpoolv1.SpiderI
 	return nil
 }
 
-func (im *ipPoolManager) validateIPPoolSpec(ctx context.Context, ipPool *spiderpoolv1.SpiderIPPool) *field.Error {
-	if err := im.validateIPPoolIPVersion(ipPool.Spec.IPVersion); err != nil {
-		return err
-	}
-	if err := im.validateIPPoolSubnet(ctx, *ipPool.Spec.IPVersion, ipPool.Name, ipPool.Spec.Subnet); err != nil {
-		return err
-	}
-	if err := im.validateIPPoolAvailableIPs(ctx, ipPool); err != nil {
+func (iw *IPPoolWebhook) validateIPPoolSpec(ctx context.Context, ipPool *spiderpoolv1.SpiderIPPool) *field.Error {
+	if err := iw.validateIPPoolAvailableIPs(ctx, ipPool); err != nil {
 		return err
 	}
 	if err := validateIPPoolGateway(*ipPool.Spec.IPVersion, ipPool.Spec.Subnet, ipPool.Spec.Gateway); err != nil {
@@ -94,10 +101,10 @@ func (im *ipPoolManager) validateIPPoolSpec(ctx context.Context, ipPool *spiderp
 	return validateIPPoolRoutes(*ipPool.Spec.IPVersion, ipPool.Spec.Subnet, ipPool.Spec.Routes)
 }
 
-func validateIPPoolIPInUse(ipPool *spiderpoolv1.SpiderIPPool, enableSubnet bool) *field.Error {
+func validateIPPoolIPInUse(ipPool *spiderpoolv1.SpiderIPPool) *field.Error {
 	totalIPs, err := spiderpoolip.AssembleTotalIPs(*ipPool.Spec.IPVersion, ipPool.Spec.IPs, ipPool.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 IPPool %s: %v", ipPool.Name, err))
 	}
 
 	totalIPsMap := map[string]bool{}
@@ -117,7 +124,7 @@ func validateIPPoolIPInUse(ipPool *spiderpoolv1.SpiderIPPool, enableSubnet bool)
 	return nil
 }
 
-func (im *ipPoolManager) validateIPPoolIPVersion(version *types.IPVersion) *field.Error {
+func (iw *IPPoolWebhook) validateIPPoolIPVersion(version *types.IPVersion) *field.Error {
 	if version == nil {
 		return field.Invalid(
 			ipVersionField,
@@ -136,14 +143,14 @@ func (im *ipPoolManager) validateIPPoolIPVersion(version *types.IPVersion) *fiel
 		)
 	}
 
-	if *version == constant.IPv4 && !im.config.EnableIPv4 {
+	if *version == constant.IPv4 && !iw.EnableIPv4 {
 		return field.Forbidden(
 			ipVersionField,
 			"IPv4 is disabled",
 		)
 	}
 
-	if *version == constant.IPv6 && !im.config.EnableIPv6 {
+	if *version == constant.IPv6 && !iw.EnableIPv6 {
 		return field.Forbidden(
 			ipVersionField,
 			"IPv6 is disabled",
@@ -153,86 +160,90 @@ func (im *ipPoolManager) validateIPPoolIPVersion(version *types.IPVersion) *fiel
 	return nil
 }
 
-func (im *ipPoolManager) validateIPPoolSubnet(ctx context.Context, version types.IPVersion, poolName, subnet string) *field.Error {
-	ipPoolList, err := im.ListIPPools(ctx)
-	if err != nil {
-		return field.InternalError(subnetField, err)
+func (iw *IPPoolWebhook) validateIPPoolSubnet(ctx context.Context, ipPool *spiderpoolv1.SpiderIPPool) *field.Error {
+	if err := spiderpoolip.IsCIDR(*ipPool.Spec.IPVersion, ipPool.Spec.Subnet); err != nil {
+		return field.Invalid(
+			subnetField,
+			ipPool.Spec.Subnet,
+			err.Error(),
+		)
 	}
 
-	for _, pool := range ipPoolList.Items {
-		if pool.Name == poolName || *pool.Spec.IPVersion != version {
-			continue
-		}
-
-		if pool.Spec.Subnet == subnet {
-			continue
-		}
-
-		overlap, err := spiderpoolip.IsCIDROverlap(version, subnet, pool.Spec.Subnet)
-		if err != nil {
-			return field.Invalid(
-				subnetField,
-				subnet,
-				err.Error(),
-			)
-		}
+	var ipPoolList spiderpoolv1.SpiderIPPoolList
+	if err := iw.Client.List(ctx, &ipPoolList); err != nil {
+		return field.InternalError(subnetField, fmt.Errorf("failed to list IPPools: %v", err))
+	}
 
-		if overlap {
-			return field.Invalid(
-				subnetField,
-				subnet,
-				fmt.Sprintf("overlap with IPPool %s which 'spec.subnet' is %s", pool.Name, pool.Spec.Subnet),
-			)
+	for _, pool := range ipPoolList.Items {
+		if *pool.Spec.IPVersion == *ipPool.Spec.IPVersion {
+			if pool.Name == ipPool.Name {
+				return field.InternalError(subnetField, fmt.Errorf("IPPool %s already exists", ipPool.Name))
+			}
+
+			if pool.Spec.Subnet == ipPool.Spec.Subnet {
+				continue
+			}
+
+			overlap, err := spiderpoolip.IsCIDROverlap(*ipPool.Spec.IPVersion, ipPool.Spec.Subnet, pool.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,
+					ipPool.Spec.Subnet,
+					fmt.Sprintf("overlap with IPPool %s which 'spec.subnet' is %s", pool.Name, pool.Spec.Subnet),
+				)
+			}
 		}
 	}
 
 	return nil
 }
 
-func (im *ipPoolManager) validateIPPoolAvailableIPs(ctx context.Context, ipPool *spiderpoolv1.SpiderIPPool) *field.Error {
-	if err := validateIPPoolIPs(*ipPool.Spec.IPVersion, ipPool.Spec.Subnet, ipPool.Spec.IPs, im.config.EnableSpiderSubnet); err != nil {
+func (iw *IPPoolWebhook) validateIPPoolAvailableIPs(ctx context.Context, ipPool *spiderpoolv1.SpiderIPPool) *field.Error {
+	if err := iw.validateIPPoolIPs(*ipPool.Spec.IPVersion, ipPool.Spec.Subnet, ipPool.Spec.IPs); err != nil {
 		return err
 	}
 	if err := validateIPPoolExcludeIPs(*ipPool.Spec.IPVersion, ipPool.Spec.Subnet, ipPool.Spec.ExcludeIPs); err != nil {
 		return err
 	}
 
-	ipPoolList, err := im.ListIPPools(ctx)
-	if err != nil {
-		return field.InternalError(ipsField, err)
+	var ipPoolList spiderpoolv1.SpiderIPPoolList
+	if err := iw.Client.List(ctx, &ipPoolList); err != nil {
+		return field.InternalError(ipsField, fmt.Errorf("failed to list IPPools: %v", err))
 	}
 
 	newIPs, err := spiderpoolip.AssembleTotalIPs(*ipPool.Spec.IPVersion, ipPool.Spec.IPs, ipPool.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 IPPool %s: %v", ipPool.Name, err))
 	}
 
 	for _, pool := range ipPoolList.Items {
-		if pool.Name == ipPool.Name || pool.Spec.Subnet != ipPool.Spec.Subnet {
-			continue
-		}
-
-		existIPs, err := spiderpoolip.AssembleTotalIPs(*pool.Spec.IPVersion, pool.Spec.IPs, pool.Spec.ExcludeIPs)
-		if err != nil {
-			return field.InternalError(ipsField, err)
-		}
-
-		overlapIPs := spiderpoolip.IPsIntersectionSet(newIPs, existIPs)
-		if len(overlapIPs) > 0 {
-			overlapRanges, _ := spiderpoolip.ConvertIPsToIPRanges(*pool.Spec.IPVersion, overlapIPs)
-			return field.Forbidden(
-				ipsField,
-				fmt.Sprintf("overlap with IPPool %s in IP ranges %v, total IP addresses of an IPPool are jointly determined by 'spec.ips' and 'spec.excludeIPs'", pool.Name, overlapRanges),
-			)
+		if pool.Spec.Subnet == ipPool.Spec.Subnet && pool.Name != ipPool.Name {
+			existIPs, err := spiderpoolip.AssembleTotalIPs(*pool.Spec.IPVersion, pool.Spec.IPs, pool.Spec.ExcludeIPs)
+			if err != nil {
+				return field.InternalError(ipsField, fmt.Errorf("failed to assemble the total IP addresses of the existing IPPool %s: %v", pool.Name, err))
+			}
+
+			overlapIPs := spiderpoolip.IPsIntersectionSet(newIPs, existIPs)
+			if len(overlapIPs) > 0 {
+				overlapRanges, _ := spiderpoolip.ConvertIPsToIPRanges(*pool.Spec.IPVersion, overlapIPs)
+				return field.Forbidden(
+					ipsField,
+					fmt.Sprintf("overlap with IPPool %s in IP ranges %v, total IP addresses of an IPPool are jointly determined by 'spec.ips' and 'spec.excludeIPs'", pool.Name, overlapRanges),
+				)
+			}
 		}
 	}
 
 	return nil
 }
 
-func validateIPPoolIPs(version types.IPVersion, subnet string, ips []string, enableSubnet bool) *field.Error {
+func (iw *IPPoolWebhook) validateIPPoolIPs(version types.IPVersion, subnet string, ips []string) *field.Error {
 	if len(ips) == 0 {
-		if enableSubnet {
+		if iw.EnableSpiderSubnet {
 			return nil
 		}
 
diff --git a/pkg/ippoolmanager/ippool_webhook.go b/pkg/ippoolmanager/ippool_webhook.go
index 0bfc1a8c9..1ab5280f9 100644
--- a/pkg/ippoolmanager/ippool_webhook.go
+++ b/pkg/ippoolmanager/ippool_webhook.go
@@ -6,158 +6,76 @@ package ippoolmanager
 import (
 	"context"
 	"errors"
-	"fmt"
 
 	"go.uber.org/zap"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	ctrl "sigs.k8s.io/controller-runtime"
+	"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 (im *ipPoolManager) SetupWebhook() error {
-	webhookLogger = logutils.Logger.Named("IPPool-Webhook")
+type IPPoolWebhook struct {
+	client.Client
+	Scheme *runtime.Scheme
 
-	return ctrl.NewWebhookManagedBy(im.runtimeMgr).
+	EnableIPv4         bool
+	EnableIPv6         bool
+	EnableSpiderSubnet bool
+}
+
+func (iw *IPPoolWebhook) SetupWebhookWithManager(mgr ctrl.Manager) error {
+	if WebhookLogger == nil {
+		WebhookLogger = logutils.Logger.Named("IPPool-Webhook")
+	}
+
+	return ctrl.NewWebhookManagedBy(mgr).
 		For(&spiderpoolv1.SpiderIPPool{}).
-		WithDefaulter(im).
-		WithValidator(im).
+		WithDefaulter(iw).
+		WithValidator(iw).
 		Complete()
 }
 
-var _ webhook.CustomDefaulter = (*ipPoolManager)(nil)
+var _ webhook.CustomDefaulter = (*IPPoolWebhook)(nil)
 
-// Default implements webhook.CustomDefaulter so a webhook will be registered for the type
-func (im *ipPoolManager) Default(ctx context.Context, obj runtime.Object) error {
-	ipPool, ok := obj.(*spiderpoolv1.SpiderIPPool)
-	if !ok {
-		return apierrors.NewBadRequest(fmt.Sprintf("mutating webhook of IPPool got an object with mismatched GVK: %+v", obj.GetObjectKind().GroupVersionKind()))
-	}
+// Default implements webhook.CustomDefaulter so a webhook will be registered for the type.
+func (iw *IPPoolWebhook) Default(ctx context.Context, obj runtime.Object) error {
+	ipPool := obj.(*spiderpoolv1.SpiderIPPool)
 
-	logger := webhookLogger.Named("Mutating").With(
+	logger := WebhookLogger.Named("Mutating").With(
 		zap.String("IPPoolName", ipPool.Name),
 		zap.String("Operation", "DEFAULT"),
 	)
-	logger.Info("Start to mutate IPPool")
 	logger.Sugar().Debugf("Request IPPool: %+v", *ipPool)
 
-	if ipPool.DeletionTimestamp != nil {
-		logger.Info("Deleting IPPool, noting to mutate")
-		return nil
-	}
-
-	if im.config.EnableSpiderSubnet {
-		if err := im.setControllerSubnet(ctx, ipPool); err != nil {
-			return apierrors.NewInternalError(fmt.Errorf("failed to set reference for controller Subnet: %v", err))
-		}
-	}
-
-	if !controllerutil.ContainsFinalizer(ipPool, constant.SpiderFinalizer) {
-		controllerutil.AddFinalizer(ipPool, constant.SpiderFinalizer)
-		logger.Sugar().Infof("Add finalizer %s", constant.SpiderFinalizer)
-	}
-
-	if ipPool.Spec.IPVersion == nil {
-		var version types.IPVersion
-		if spiderpoolip.IsIPv4CIDR(ipPool.Spec.Subnet) {
-			version = constant.IPv4
-		} else if spiderpoolip.IsIPv6CIDR(ipPool.Spec.Subnet) {
-			version = constant.IPv6
-		} else {
-			logger.Error("Invalid 'spec.ipVersion', noting to mutate")
-			return nil
-		}
-
-		ipPool.Spec.IPVersion = new(types.IPVersion)
-		*ipPool.Spec.IPVersion = version
-		logger.Sugar().Infof("Set 'spec.ipVersion' to %d", version)
-	}
-
-	if len(ipPool.Spec.IPs) > 1 {
-		mergedIPs, err := spiderpoolip.MergeIPRanges(*ipPool.Spec.IPVersion, ipPool.Spec.IPs)
-		if err != nil {
-			logger.Sugar().Errorf("Failed to merge 'spec.ips': %v", err)
-		} else {
-			ipPool.Spec.IPs = mergedIPs
-			logger.Sugar().Debugf("Merge 'spec.ips':\n%v\n\nto:\n\n%v", ipPool.Spec.IPs, mergedIPs)
-		}
-	}
-
-	if len(ipPool.Spec.ExcludeIPs) > 1 {
-		mergedExcludeIPs, err := spiderpoolip.MergeIPRanges(*ipPool.Spec.IPVersion, ipPool.Spec.ExcludeIPs)
-		if err != nil {
-			logger.Sugar().Errorf("Failed to merge 'spec.excludeIPs': %v", err)
-		} else {
-			ipPool.Spec.ExcludeIPs = mergedExcludeIPs
-			logger.Sugar().Debugf("Merge 'spec.excludeIPs':\n%v\n\nto:\n\n%v", ipPool.Spec.ExcludeIPs, mergedExcludeIPs)
-		}
+	if err := iw.mutateIPPool(logutils.IntoContext(ctx, logger), ipPool); err != nil {
+		logger.Sugar().Errorf("Failed to mutate IPPool: %v", err)
 	}
 
 	return nil
 }
 
-func (im *ipPoolManager) setControllerSubnet(ctx context.Context, ipPool *spiderpoolv1.SpiderIPPool) error {
-	logger := logutils.FromContext(ctx)
+var _ webhook.CustomValidator = (*IPPoolWebhook)(nil)
 
-	// TODO(iiiceoo): Should not List every time.
-	subnetList, err := im.subnetManager.ListSubnets(ctx)
-	if err != nil {
-		return err
-	}
+// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type.
+func (iw *IPPoolWebhook) ValidateCreate(ctx context.Context, obj runtime.Object) error {
+	ipPool := obj.(*spiderpoolv1.SpiderIPPool)
 
-	for _, subnet := range subnetList.Items {
-		if subnet.Spec.Subnet == ipPool.Spec.Subnet {
-			if !metav1.IsControlledBy(ipPool, &subnet) {
-				if err := ctrl.SetControllerReference(&subnet, ipPool, im.runtimeMgr.GetScheme()); err != nil {
-					return err
-				}
-				logger.Sugar().Infof("Set owner reference as Subnet %s", subnet.Name)
-			}
-
-			if ipPool.Labels == nil {
-				ipPool.Labels = make(map[string]string)
-			}
-			if v, ok := ipPool.Labels[constant.LabelIPPoolOwnerSpiderSubnet]; !ok || v != subnet.Name {
-				ipPool.Labels[constant.LabelIPPoolOwnerSpiderSubnet] = subnet.Name
-				logger.Sugar().Infof("Set label %s: %s", constant.LabelIPPoolOwnerSpiderSubnet, subnet.Name)
-			}
-			break
-		}
-	}
-
-	return nil
-}
-
-var _ webhook.CustomValidator = (*ipPoolManager)(nil)
-
-// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type
-func (im *ipPoolManager) ValidateCreate(ctx context.Context, obj runtime.Object) error {
-	ipPool, ok := obj.(*spiderpoolv1.SpiderIPPool)
-	if !ok {
-		return apierrors.NewBadRequest(fmt.Sprintf("validating webhook of IPPool got an object with mismatched GVK: %+v", obj.GetObjectKind().GroupVersionKind()))
-	}
-	if im.config.EnableSpiderSubnet && im.subnetManager == nil {
-		return apierrors.NewInternalError(errors.New("subnet manager must be injected when the feature SpiderSubnet is enabled"))
-	}
-
-	logger := webhookLogger.Named("Validating").With(
+	logger := WebhookLogger.Named("Validating").With(
 		zap.String("IPPoolName", ipPool.Name),
 		zap.String("Operation", "CREATE"),
 	)
 	logger.Sugar().Debugf("Request IPPool: %+v", *ipPool)
 
-	if errs := im.validateCreateIPPoolAndUpdateSubnetFreeIPs(logutils.IntoContext(ctx, logger), ipPool); len(errs) != 0 {
+	if errs := iw.validateCreateIPPoolWhileEnableSpiderSubnet(logutils.IntoContext(ctx, logger), ipPool); len(errs) != 0 {
 		logger.Sugar().Errorf("Failed to create IPPool: %v", errs.ToAggregate().Error())
 		return apierrors.NewInvalid(
 			schema.GroupKind{Group: constant.SpiderpoolAPIGroup, Kind: constant.SpiderIPPoolKind},
@@ -169,18 +87,12 @@ func (im *ipPoolManager) ValidateCreate(ctx context.Context, obj runtime.Object)
 	return nil
 }
 
-// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type
-func (im *ipPoolManager) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) error {
-	oldIPPool, _ := oldObj.(*spiderpoolv1.SpiderIPPool)
-	newIPPool, ok := newObj.(*spiderpoolv1.SpiderIPPool)
-	if !ok {
-		return apierrors.NewBadRequest(fmt.Sprintf("validating webhook of IPPool got an object with mismatched GVK: %+v", newObj.GetObjectKind().GroupVersionKind()))
-	}
-	if im.config.EnableSpiderSubnet && im.subnetManager == nil {
-		return apierrors.NewInternalError(errors.New("subnet manager must be injected when the feature SpiderSubnet is enabled"))
-	}
+// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type.
+func (iw *IPPoolWebhook) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) error {
+	oldIPPool := oldObj.(*spiderpoolv1.SpiderIPPool)
+	newIPPool := newObj.(*spiderpoolv1.SpiderIPPool)
 
-	logger := webhookLogger.Named("Validating").With(
+	logger := WebhookLogger.Named("Validating").With(
 		zap.String("IPPoolName", newIPPool.Name),
 		zap.String("Operation", "UPDATE"),
 	)
@@ -199,7 +111,7 @@ func (im *ipPoolManager) ValidateUpdate(ctx context.Context, oldObj, newObj runt
 		)
 	}
 
-	if errs := im.validateUpdateIPPoolAndUpdateSubnetFreeIPs(logutils.IntoContext(ctx, logger), oldIPPool, newIPPool); len(errs) != 0 {
+	if errs := iw.validateUpdateIPPoolWhileEnableSpiderSubnet(logutils.IntoContext(ctx, logger), oldIPPool, newIPPool); len(errs) != 0 {
 		logger.Sugar().Errorf("Failed to update IPPool: %v", errs.ToAggregate().Error())
 		return apierrors.NewInvalid(
 			schema.GroupKind{Group: constant.SpiderpoolAPIGroup, Kind: constant.SpiderIPPoolKind},
@@ -211,7 +123,7 @@ func (im *ipPoolManager) ValidateUpdate(ctx context.Context, oldObj, newObj runt
 	return nil
 }
 
-// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type
-func (im *ipPoolManager) ValidateDelete(ctx context.Context, obj runtime.Object) error {
+// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type.
+func (iw *IPPoolWebhook) ValidateDelete(ctx context.Context, obj runtime.Object) error {
 	return nil
 }
diff --git a/pkg/ippoolmanager/types/interface.go b/pkg/ippoolmanager/types/interface.go
index b611dec50..4ebb44053 100644
--- a/pkg/ippoolmanager/types/interface.go
+++ b/pkg/ippoolmanager/types/interface.go
@@ -28,7 +28,6 @@ const (
 
 type IPPoolManager interface {
 	Start(ctx context.Context) error
-	SetupWebhook() error
 	SetupInformer(client crdclientset.Interface, controllerLeader election.SpiderLeaseElector) error
 	InjectSubnetManager(subnetManager subnetmanagertypes.SubnetManager)
 	GetIPPoolByName(ctx context.Context, poolName string) (*spiderpoolv1.SpiderIPPool, error)


Test patch:
diff --git a/pkg/ippoolmanager/ippool_manager_suite_test.go b/pkg/ippoolmanager/ippool_manager_suite_test.go
new file mode 100644
index 000000000..8187b7d1e
--- /dev/null
+++ b/pkg/ippoolmanager/ippool_manager_suite_test.go
@@ -0,0 +1,44 @@
+// Copyright 2022 Authors of spidernet-io
+// SPDX-License-Identifier: Apache-2.0
+
+package ippoolmanager_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"
+
+	"github.com/spidernet-io/spiderpool/pkg/ippoolmanager"
+	spiderpoolv1 "github.com/spidernet-io/spiderpool/pkg/k8s/apis/spiderpool.spidernet.io/v1"
+)
+
+var scheme *runtime.Scheme
+var fakeClient client.Client
+var ipPoolWebhook *ippoolmanager.IPPoolWebhook
+
+func TestIPPoolManager(t *testing.T) {
+	RegisterFailHandler(Fail)
+	RunSpecs(t, "IPPoolManager Suite", Label("ippoolmanager", "unitest"))
+}
+
+var _ = BeforeSuite(func() {
+	scheme = runtime.NewScheme()
+	err := spiderpoolv1.AddToScheme(scheme)
+	Expect(err).NotTo(HaveOccurred())
+
+	fakeClient = fake.NewClientBuilder().
+		WithScheme(scheme).
+		Build()
+
+	ipPoolWebhook = &ippoolmanager.IPPoolWebhook{
+		Client:             fakeClient,
+		Scheme:             scheme,
+		EnableIPv4:         true,
+		EnableIPv6:         true,
+		EnableSpiderSubnet: true,
+	}
+})
diff --git a/pkg/ippoolmanager/ippool_webhook_test.go b/pkg/ippoolmanager/ippool_webhook_test.go
new file mode 100644
index 000000000..61aec75fc
--- /dev/null
+++ b/pkg/ippoolmanager/ippool_webhook_test.go
@@ -0,0 +1,1760 @@
+// Copyright 2022 Authors of spidernet-io
+// SPDX-License-Identifier: Apache-2.0
+
+package ippoolmanager_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/apimachinery/pkg/util/uuid"
+	"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"
+	"github.com/spidernet-io/spiderpool/pkg/ippoolmanager"
+	spiderpoolv1 "github.com/spidernet-io/spiderpool/pkg/k8s/apis/spiderpool.spidernet.io/v1"
+	"github.com/spidernet-io/spiderpool/pkg/logutils"
+)
+
+var _ = Describe("IPPoolWebhook", Label("ippool_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 = ipPoolWebhook.SetupWebhookWithManager(mgr)
+			Expect(err).NotTo(HaveOccurred())
+		})
+	})
+
+	Describe("Test IPPoolWebhook's method", func() {
+		var count uint64
+		var subnetName string
+		var subnetT *spiderpoolv1.SpiderSubnet
+		var ipPoolName, existIPPoolName string
+		var ipPoolT, existIPPoolT *spiderpoolv1.SpiderIPPool
+
+		BeforeEach(func() {
+			ippoolmanager.WebhookLogger = logutils.Logger.Named("IPPool-Webhook")
+			ipPoolWebhook.EnableIPv4 = true
+			ipPoolWebhook.EnableIPv6 = true
+			ipPoolWebhook.EnableSpiderSubnet = false
+
+			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{},
+			}
+
+			ipPoolName = fmt.Sprintf("ippool-%v", count)
+			ipPoolT = &spiderpoolv1.SpiderIPPool{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       constant.SpiderIPPoolKind,
+					APIVersion: fmt.Sprintf("%s/%s", constant.SpiderpoolAPIGroup, constant.SpiderpoolAPIVersionV1),
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name: ipPoolName,
+				},
+				Spec: spiderpoolv1.IPPoolSpec{},
+			}
+
+			existIPPoolName = fmt.Sprintf("z-exist-ippool-%v", count)
+			existIPPoolT = &spiderpoolv1.SpiderIPPool{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       constant.SpiderIPPoolKind,
+					APIVersion: fmt.Sprintf("%s/%s", constant.SpiderpoolAPIGroup, constant.SpiderpoolAPIVersionV1),
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name: existIPPoolName,
+				},
+				Spec: spiderpoolv1.IPPoolSpec{},
+			}
+		})
+
+		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, ipPoolT, deleteOption)
+			Expect(client.IgnoreNotFound(err)).NotTo(HaveOccurred())
+
+			err = fakeClient.Delete(ctx, existIPPoolT, deleteOption)
+			Expect(client.IgnoreNotFound(err)).NotTo(HaveOccurred())
+
+			err = fakeClient.Delete(ctx, subnetT, deleteOption)
+			Expect(client.IgnoreNotFound(err)).NotTo(HaveOccurred())
+		})
+
+		Describe("Default", func() {
+			It("avoids modifying the terminating IPPool", func() {
+				now := metav1.Now()
+				ipPoolT.SetDeletionTimestamp(&now)
+
+				ctx := context.TODO()
+				err := ipPoolWebhook.Default(ctx, ipPoolT)
+				Expect(err).NotTo(HaveOccurred())
+			})
+
+			It("failed to list Subnets due to some unknown errors", func() {
+				patches := gomonkey.ApplyMethodReturn(fakeClient, "List", constant.ErrUnknown)
+				defer patches.Reset()
+
+				ipPoolWebhook.EnableSpiderSubnet = true
+				subnetT.SetUID(uuid.NewUUID())
+				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())
+
+				ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+				ipPoolT.Spec.Subnet = "172.18.40.0/24"
+
+				err = ipPoolWebhook.Default(ctx, ipPoolT)
+				Expect(err).NotTo(HaveOccurred())
+
+				controlled := metav1.IsControlledBy(ipPoolT, subnetT)
+				Expect(controlled).NotTo(BeTrue())
+
+				v, ok := ipPoolT.Labels[constant.LabelIPPoolOwnerSpiderSubnet]
+				Expect(ok).NotTo(BeTrue())
+				Expect(v).NotTo(Equal(subnetName))
+			})
+
+			It("failed to set owner reference due to some unknown errors", func() {
+				patches := gomonkey.ApplyFuncReturn(controllerutil.SetControllerReference, constant.ErrUnknown)
+				defer patches.Reset()
+
+				ipPoolWebhook.EnableSpiderSubnet = true
+				subnetT.SetUID(uuid.NewUUID())
+				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())
+
+				ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+				ipPoolT.Spec.Subnet = "172.18.40.0/24"
+
+				err = ipPoolWebhook.Default(ctx, ipPoolT)
+				Expect(err).NotTo(HaveOccurred())
+
+				controlled := metav1.IsControlledBy(ipPoolT, subnetT)
+				Expect(controlled).NotTo(BeTrue())
+
+				v, ok := ipPoolT.Labels[constant.LabelIPPoolOwnerSpiderSubnet]
+				Expect(ok).NotTo(BeTrue())
+				Expect(v).NotTo(Equal(subnetName))
+			})
+
+			It("sets the reference of the controller Subnet", func() {
+				ipPoolWebhook.EnableSpiderSubnet = true
+				subnetT.SetUID(uuid.NewUUID())
+				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())
+
+				ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+				ipPoolT.Spec.Subnet = "172.18.40.0/24"
+
+				err = ipPoolWebhook.Default(ctx, ipPoolT)
+				Expect(err).NotTo(HaveOccurred())
+
+				controlled := metav1.IsControlledBy(ipPoolT, subnetT)
+				Expect(controlled).To(BeTrue())
+
+				v, ok := ipPoolT.Labels[constant.LabelIPPoolOwnerSpiderSubnet]
+				Expect(ok).To(BeTrue())
+				Expect(v).To(Equal(subnetName))
+
+				err = ipPoolWebhook.Default(ctx, ipPoolT)
+				Expect(err).NotTo(HaveOccurred())
+			})
+
+			It("adds finalizer", func() {
+				ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+
+				ctx := context.TODO()
+				err := ipPoolWebhook.Default(ctx, ipPoolT)
+				Expect(err).NotTo(HaveOccurred())
+
+				contains := controllerutil.ContainsFinalizer(ipPoolT, constant.SpiderFinalizer)
+				Expect(contains).To(BeTrue())
+			})
+
+			It("failed to set 'spec.ipVersion' due to the invalid 'spec.subnet'", func() {
+				ipPoolT.Spec.Subnet = constant.InvalidCIDR
+
+				ctx := context.TODO()
+				err := ipPoolWebhook.Default(ctx, ipPoolT)
+				Expect(err).NotTo(HaveOccurred())
+				Expect(ipPoolT.Spec.IPVersion).To(BeNil())
+			})
+
+			It("sets 'spec.ipVersion' to 4", func() {
+				ipPoolT.Spec.Subnet = "172.18.40.0/24"
+
+				ctx := context.TODO()
+				err := ipPoolWebhook.Default(ctx, ipPoolT)
+				Expect(err).NotTo(HaveOccurred())
+				Expect(*ipPoolT.Spec.IPVersion).To(Equal(constant.IPv4))
+			})
+
+			It("sets 'spec.ipVersion' to 6", func() {
+				ipPoolT.Spec.Subnet = "abcd:1234::/120"
+
+				ctx := context.TODO()
+				err := ipPoolWebhook.Default(ctx, ipPoolT)
+				Expect(err).NotTo(HaveOccurred())
+				Expect(*ipPoolT.Spec.IPVersion).To(Equal(constant.IPv6))
+			})
+
+			It("failed to merge 'spec.ips' due to the invalid 'spec.ipVersion'", func() {
+				ipPoolT.Spec.IPVersion = pointer.Int64(constant.InvalidIPVersion)
+				ipPoolT.Spec.IPs = append(ipPoolT.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 := ipPoolWebhook.Default(ctx, ipPoolT)
+				Expect(err).NotTo(HaveOccurred())
+				Expect(ipPoolT.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() {
+				ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+				ipPoolT.Spec.IPs = append(ipPoolT.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 := ipPoolWebhook.Default(ctx, ipPoolT)
+				Expect(err).NotTo(HaveOccurred())
+				Expect(ipPoolT.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() {
+				ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+				ipPoolT.Spec.IPs = append(ipPoolT.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 := ipPoolWebhook.Default(ctx, ipPoolT)
+				Expect(err).NotTo(HaveOccurred())
+				Expect(ipPoolT.Spec.IPs).To(Equal(
+					[]string{
+						"172.18.40.1-172.18.40.3",
+						"172.18.40.10",
+					},
+				))
+			})
+
+			It("merges IPv6 'spec.ips'", func() {
+				ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv6)
+				ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+					[]string{
+						"abcd:1234::a",
+						"abcd:1234::1-abcd:1234::2",
+						"abcd:1234::2-abcd:1234::3",
+					}...,
+				)
+
+				ctx := context.TODO()
+				err := ipPoolWebhook.Default(ctx, ipPoolT)
+				Expect(err).NotTo(HaveOccurred())
+				Expect(ipPoolT.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() {
+				ipPoolT.Spec.IPVersion = pointer.Int64(constant.InvalidIPVersion)
+				ipPoolT.Spec.ExcludeIPs = append(ipPoolT.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 := ipPoolWebhook.Default(ctx, ipPoolT)
+				Expect(err).NotTo(HaveOccurred())
+				Expect(ipPoolT.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() {
+				ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+				ipPoolT.Spec.ExcludeIPs = append(ipPoolT.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 := ipPoolWebhook.Default(ctx, ipPoolT)
+				Expect(err).NotTo(HaveOccurred())
+				Expect(ipPoolT.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() {
+				ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+				ipPoolT.Spec.ExcludeIPs = append(ipPoolT.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 := ipPoolWebhook.Default(ctx, ipPoolT)
+				Expect(err).NotTo(HaveOccurred())
+				Expect(ipPoolT.Spec.ExcludeIPs).To(Equal(
+					[]string{
+						"172.18.40.1-172.18.40.3",
+						"172.18.40.10",
+					},
+				))
+			})
+
+			It("merges IPv6 'spec.excludeIPs'", func() {
+				ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv6)
+				ipPoolT.Spec.ExcludeIPs = append(ipPoolT.Spec.ExcludeIPs,
+					[]string{
+						"abcd:1234::a",
+						"abcd:1234::1-abcd:1234::2",
+						"abcd:1234::2-abcd:1234::3",
+					}...,
+				)
+
+				ctx := context.TODO()
+				err := ipPoolWebhook.Default(ctx, ipPoolT)
+				Expect(err).NotTo(HaveOccurred())
+				Expect(ipPoolT.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() {
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					ctx := context.TODO()
+					err := ipPoolWebhook.ValidateCreate(ctx, ipPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("inputs invalid 'spec.ipVersion'", func() {
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.InvalidIPVersion)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					ctx := context.TODO()
+					err := ipPoolWebhook.ValidateCreate(ctx, ipPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("creates IPv4 IPPool but IPv4 is disbale'", func() {
+					ipPoolWebhook.EnableIPv4 = false
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					ctx := context.TODO()
+					err := ipPoolWebhook.ValidateCreate(ctx, ipPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("creates IPv6 IPPool but IPv6 is disbale'", func() {
+					ipPoolWebhook.EnableIPv6 = false
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv6)
+					ipPoolT.Spec.Subnet = "adbc:1234::/120"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+						[]string{
+							"adbc:1234::1-adbc:1234::2",
+							"adbc:1234::a",
+						}...,
+					)
+
+					ctx := context.TODO()
+					err := ipPoolWebhook.ValidateCreate(ctx, ipPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+			})
+
+			When("Validating 'spec.subnet'", func() {
+				It("inputs invalid 'spec.subnet'", func() {
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = constant.InvalidCIDR
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					ctx := context.TODO()
+					err := ipPoolWebhook.ValidateCreate(ctx, ipPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("failed to list IPPools due to some unknown errors", func() {
+					patches := gomonkey.ApplyMethodReturn(fakeClient, "List", constant.ErrUnknown)
+					defer patches.Reset()
+
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					ctx := context.TODO()
+					err := ipPoolWebhook.ValidateCreate(ctx, ipPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("creates an existing IPPool", func() {
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					ctx := context.TODO()
+					err := fakeClient.Create(ctx, ipPoolT)
+					Expect(err).NotTo(HaveOccurred())
+
+					err = ipPoolWebhook.ValidateCreate(ctx, ipPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("creates an IPPool with the same 'spec.subnet'", func() {
+					existIPPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					existIPPoolT.Spec.Subnet = "172.18.40.0/24"
+					existIPPoolT.Spec.IPs = append(existIPPoolT.Spec.IPs, "172.18.41.1-172.18.41.2")
+
+					ctx := context.TODO()
+					err := fakeClient.Create(ctx, existIPPoolT)
+					Expect(err).NotTo(HaveOccurred())
+
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs, "172.18.40.10")
+
+					err = ipPoolWebhook.ValidateCreate(ctx, ipPoolT)
+					Expect(err).NotTo(HaveOccurred())
+				})
+
+				It("failed to compare 'spec.subnet' with existing Subnet", func() {
+					existIPPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					existIPPoolT.Spec.Subnet = constant.InvalidCIDR
+					existIPPoolT.Spec.IPs = append(existIPPoolT.Spec.IPs, "172.18.41.1-172.18.41.2")
+
+					ctx := context.TODO()
+					err := fakeClient.Create(ctx, existIPPoolT)
+					Expect(err).NotTo(HaveOccurred())
+
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					err = ipPoolWebhook.ValidateCreate(ctx, ipPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("overlaps with existing Subnet", func() {
+					existIPPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					existIPPoolT.Spec.Subnet = "172.18.40.0/25"
+					existIPPoolT.Spec.IPs = append(existIPPoolT.Spec.IPs, "172.18.40.40")
+
+					ctx := context.TODO()
+					err := fakeClient.Create(ctx, existIPPoolT)
+					Expect(err).NotTo(HaveOccurred())
+
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					err = ipPoolWebhook.ValidateCreate(ctx, ipPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+			})
+
+			When("Validating 'spec.ips'", func() {
+				It("inputs empty 'spec.ips' while feature SpiderSubnet is enabled", func() {
+					ipPoolWebhook.EnableSpiderSubnet = true
+					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 = controllerutil.SetControllerReference(subnetT, ipPoolT, scheme)
+					Expect(err).NotTo(HaveOccurred())
+
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+
+					err = ipPoolWebhook.ValidateCreate(ctx, ipPoolT)
+					Expect(err).NotTo(HaveOccurred())
+				})
+
+				It("inputs empty 'spec.ips'", func() {
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+
+					ctx := context.TODO()
+					err := ipPoolWebhook.ValidateCreate(ctx, ipPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("inputs invalid 'spec.ips'", func() {
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs, constant.InvalidIPRange)
+
+					ctx := context.TODO()
+					err := ipPoolWebhook.ValidateCreate(ctx, ipPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("inputs 'spec.ips' that do not pertains to 'spec.subnet'", func() {
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.41.10",
+						}...,
+					)
+
+					ctx := context.TODO()
+					err := ipPoolWebhook.ValidateCreate(ctx, ipPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				PIt("failed to list IPPools due to some unknown errors", func() {})
+
+				PIt("failed to assemble the total IP addresses of the IPPool due to some unknown errors", func() {})
+
+				It("exists invalid IPPool in the cluster", func() {
+					existIPPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					existIPPoolT.Spec.Subnet = "172.18.40.0/24"
+					existIPPoolT.Spec.IPs = append(existIPPoolT.Spec.IPs, constant.InvalidIPRange)
+
+					ctx := context.TODO()
+					err := fakeClient.Create(ctx, existIPPoolT)
+					Expect(err).NotTo(HaveOccurred())
+
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					err = ipPoolWebhook.ValidateCreate(ctx, ipPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("overlaps with existing IPPool", func() {
+					existIPPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					existIPPoolT.Spec.Subnet = "172.18.40.0/24"
+					existIPPoolT.Spec.IPs = append(existIPPoolT.Spec.IPs, "172.18.40.10")
+
+					ctx := context.TODO()
+					err := fakeClient.Create(ctx, existIPPoolT)
+					Expect(err).NotTo(HaveOccurred())
+
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					err = ipPoolWebhook.ValidateCreate(ctx, ipPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+			})
+
+			When("Validating 'spec.excludeIPs'", func() {
+				It("inputs invalid 'spec.excludeIPs'", func() {
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+					ipPoolT.Spec.ExcludeIPs = append(ipPoolT.Spec.ExcludeIPs, constant.InvalidIPRange)
+
+					ctx := context.TODO()
+					err := ipPoolWebhook.ValidateCreate(ctx, ipPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("inputs 'spec.excludeIPs' that do not pertains to 'spec.subnet'", func() {
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+					ipPoolT.Spec.ExcludeIPs = append(ipPoolT.Spec.ExcludeIPs, "172.18.41.10")
+
+					ctx := context.TODO()
+					err := ipPoolWebhook.ValidateCreate(ctx, ipPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+			})
+
+			When("Validating 'spec.gateway'", func() {
+				It("inputs invalid 'spec.gateway'", func() {
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+					ipPoolT.Spec.Gateway = pointer.String(constant.InvalidIP)
+
+					ctx := context.TODO()
+					err := ipPoolWebhook.ValidateCreate(ctx, ipPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("inputs 'spec.gateway' that do not pertains to 'spec.subnet'", func() {
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+					ipPoolT.Spec.Gateway = pointer.String("172.18.41.1")
+
+					ctx := context.TODO()
+					err := ipPoolWebhook.ValidateCreate(ctx, ipPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+			})
+
+			When("Validating 'spec.routes'", func() {
+				It("inputs invalid destination", func() {
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+						[]string{
+							"172.18.40.2-172.18.40.3",
+							"172.18.40.10",
+						}...,
+					)
+					ipPoolT.Spec.Routes = append(ipPoolT.Spec.Routes,
+						spiderpoolv1.Route{
+							Dst: constant.InvalidCIDR,
+							Gw:  "172.18.40.1",
+						},
+					)
+
+					ctx := context.TODO()
+					err := ipPoolWebhook.ValidateCreate(ctx, ipPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("inputs invalid gateway", func() {
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+						[]string{
+							"172.18.40.2-172.18.40.3",
+							"172.18.40.10",
+						}...,
+					)
+					ipPoolT.Spec.Routes = append(ipPoolT.Spec.Routes,
+						spiderpoolv1.Route{
+							Dst: "192.168.40.0/24",
+							Gw:  constant.InvalidIP,
+						},
+					)
+
+					ctx := context.TODO()
+					err := ipPoolWebhook.ValidateCreate(ctx, ipPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("inputs gateway that do not pertains to 'spec.subnet'", func() {
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+						[]string{
+							"172.18.40.2-172.18.40.3",
+							"172.18.40.10",
+						}...,
+					)
+					ipPoolT.Spec.Routes = append(ipPoolT.Spec.Routes,
+						spiderpoolv1.Route{
+							Dst: "192.168.40.0/24",
+							Gw:  "172.18.41.1",
+						},
+					)
+
+					ctx := context.TODO()
+					err := ipPoolWebhook.ValidateCreate(ctx, ipPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+			})
+
+			When("Validating the existence of the controller Subnet", func() {
+				BeforeEach(func() {
+					ipPoolWebhook.EnableSpiderSubnet = true
+				})
+
+				It("is orphan IPPool", func() {
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					ctx := context.TODO()
+					err := ipPoolWebhook.ValidateCreate(ctx, ipPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("sets owner reference to non-existent Subnet", func() {
+					subnetT.SetUID(uuid.NewUUID())
+					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 := controllerutil.SetControllerReference(subnetT, ipPoolT, scheme)
+					Expect(err).NotTo(HaveOccurred())
+
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					ctx := context.TODO()
+					err = ipPoolWebhook.ValidateCreate(ctx, ipPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("sets owner reference to a Subnet with different 'spec.subnet'", func() {
+					subnetT.SetUID(uuid.NewUUID())
+					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 = controllerutil.SetControllerReference(subnetT, ipPoolT, scheme)
+					Expect(err).NotTo(HaveOccurred())
+
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/25"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					err = ipPoolWebhook.ValidateCreate(ctx, ipPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("sets owner reference to a terminating Subnet", func() {
+					controllerutil.AddFinalizer(subnetT, constant.SpiderFinalizer)
+					subnetT.SetUID(uuid.NewUUID())
+					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 = fakeClient.Delete(ctx, subnetT)
+					Expect(err).NotTo(HaveOccurred())
+
+					err = controllerutil.SetControllerReference(subnetT, ipPoolT, scheme)
+					Expect(err).NotTo(HaveOccurred())
+
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					err = ipPoolWebhook.ValidateCreate(ctx, ipPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+			})
+
+			When("Validating the total IP addresses contained in the controller Subnet", func() {
+				BeforeEach(func() {
+					ipPoolWebhook.EnableSpiderSubnet = true
+				})
+
+				PIt("failed to assemble the total IP addresses of the IPPool due to some unknown errors", func() {})
+
+				PIt("failed to assemble the total IP addresses of the Subnet due to some unknown errors", func() {})
+
+				It("is out of the IP range of the Subnet", func() {
+					subnetT.SetUID(uuid.NewUUID())
+					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 = controllerutil.SetControllerReference(subnetT, ipPoolT, scheme)
+					Expect(err).NotTo(HaveOccurred())
+
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.40",
+						}...,
+					)
+
+					err = ipPoolWebhook.ValidateCreate(ctx, ipPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+			})
+
+			It("creates IPv4 Subnet with all fields valid", func() {
+				ipPoolWebhook.EnableSpiderSubnet = true
+				subnetT.SetUID(uuid.NewUUID())
+				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",
+					}...,
+				)
+
+				ctx := context.TODO()
+				err := fakeClient.Create(ctx, subnetT)
+				Expect(err).NotTo(HaveOccurred())
+
+				err = controllerutil.SetControllerReference(subnetT, ipPoolT, scheme)
+				Expect(err).NotTo(HaveOccurred())
+
+				ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+				ipPoolT.Spec.Subnet = "172.18.40.0/24"
+				ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+					[]string{
+						"172.18.40.2-172.18.40.3",
+						"172.18.40.10",
+					}...,
+				)
+				ipPoolT.Spec.ExcludeIPs = append(ipPoolT.Spec.ExcludeIPs, "172.18.40.10")
+				ipPoolT.Spec.Gateway = pointer.String("172.18.40.1")
+				ipPoolT.Spec.Vlan = pointer.Int64(0)
+				ipPoolT.Spec.Routes = append(ipPoolT.Spec.Routes,
+					spiderpoolv1.Route{
+						Dst: "192.168.40.0/24",
+						Gw:  "172.18.40.40",
+					},
+				)
+
+				err = ipPoolWebhook.ValidateCreate(ctx, ipPoolT)
+				Expect(err).NotTo(HaveOccurred())
+			})
+
+			It("creates IPv6 Subnet with all fields valid", func() {
+				ipPoolWebhook.EnableSpiderSubnet = true
+				subnetT.SetUID(uuid.NewUUID())
+				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",
+					}...,
+				)
+
+				ctx := context.TODO()
+				err := fakeClient.Create(ctx, subnetT)
+				Expect(err).NotTo(HaveOccurred())
+
+				err = controllerutil.SetControllerReference(subnetT, ipPoolT, scheme)
+				Expect(err).NotTo(HaveOccurred())
+
+				ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv6)
+				ipPoolT.Spec.Subnet = "abcd:1234::/120"
+				ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+					[]string{
+						"abcd:1234::2-abcd:1234::3",
+						"abcd:1234::a",
+					}...,
+				)
+				ipPoolT.Spec.ExcludeIPs = append(ipPoolT.Spec.ExcludeIPs, "abcd:1234::a")
+				ipPoolT.Spec.Gateway = pointer.String("abcd:1234::1")
+				ipPoolT.Spec.Vlan = pointer.Int64(0)
+				ipPoolT.Spec.Routes = append(ipPoolT.Spec.Routes,
+					spiderpoolv1.Route{
+						Dst: "fd00:40::/120",
+						Gw:  "abcd:1234::28",
+					},
+				)
+
+				err = ipPoolWebhook.ValidateCreate(ctx, ipPoolT)
+				Expect(err).NotTo(HaveOccurred())
+			})
+		})
+
+		Describe("ValidateUpdate", func() {
+			When("Validating 'spec.ipVersion'", func() {
+				It("updates 'spec.ipVersion' to nil", func() {
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					newIPPoolT := ipPoolT.DeepCopy()
+					newIPPoolT.Spec.IPVersion = nil
+
+					ctx := context.TODO()
+					err := ipPoolWebhook.ValidateUpdate(ctx, ipPoolT, newIPPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("changes 'spec.ipVersion'", func() {
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					newIPPoolT := ipPoolT.DeepCopy()
+					newIPPoolT.Spec.IPVersion = pointer.Int64(constant.IPv6)
+
+					ctx := context.TODO()
+					err := ipPoolWebhook.ValidateUpdate(ctx, ipPoolT, newIPPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("updates IPv4 IPPool but IPv4 is disbale'", func() {
+					ipPoolWebhook.EnableIPv4 = false
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs, "172.18.40.1-172.18.40.2")
+
+					newIPPoolT := ipPoolT.DeepCopy()
+					newIPPoolT.Spec.IPs = append(newIPPoolT.Spec.IPs, "172.18.40.10")
+
+					ctx := context.TODO()
+					err := ipPoolWebhook.ValidateUpdate(ctx, ipPoolT, newIPPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("updates IPv6 IPPool but IPv6 is disbale'", func() {
+					ipPoolWebhook.EnableIPv6 = false
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv6)
+					ipPoolT.Spec.Subnet = "adbc:1234::/120"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs, "adbc:1234::1-adbc:1234::2")
+
+					newIPPoolT := ipPoolT.DeepCopy()
+					newIPPoolT.Spec.IPs = append(newIPPoolT.Spec.IPs, "adbc:1234::a")
+
+					ctx := context.TODO()
+					err := ipPoolWebhook.ValidateUpdate(ctx, ipPoolT, newIPPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+			})
+
+			When("Validating 'spec.subnet'", func() {
+				It("changes 'spec.subnet'", func() {
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					newIPPoolT := ipPoolT.DeepCopy()
+					newIPPoolT.Spec.Subnet = "172.18.40.0/25"
+
+					ctx := context.TODO()
+					err := ipPoolWebhook.ValidateUpdate(ctx, ipPoolT, newIPPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+			})
+
+			When("Validating 'spec.ips'", func() {
+				It("removes all 'spec.ips' while feature SpiderSubnet is enabled", func() {
+					ipPoolWebhook.EnableSpiderSubnet = true
+					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 = controllerutil.SetControllerReference(subnetT, ipPoolT, scheme)
+					Expect(err).NotTo(HaveOccurred())
+
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					newIPPoolT := ipPoolT.DeepCopy()
+					newIPPoolT.Spec.IPs = nil
+
+					err = ipPoolWebhook.ValidateUpdate(ctx, ipPoolT, newIPPoolT)
+					Expect(err).NotTo(HaveOccurred())
+				})
+
+				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",
+						}...,
+					)
+
+					ctx := context.TODO()
+					err := fakeClient.Create(ctx, subnetT)
+					Expect(err).NotTo(HaveOccurred())
+
+					err = controllerutil.SetControllerReference(subnetT, ipPoolT, scheme)
+					Expect(err).NotTo(HaveOccurred())
+
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					newIPPoolT := ipPoolT.DeepCopy()
+					newIPPoolT.Spec.IPs = nil
+
+					err = ipPoolWebhook.ValidateUpdate(ctx, ipPoolT, newIPPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("appends invalid IP range to 'spec.ips'", func() {
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					newIPPoolT := ipPoolT.DeepCopy()
+					newIPPoolT.Spec.IPs = append(newIPPoolT.Spec.IPs, constant.InvalidIPRange)
+
+					ctx := context.TODO()
+					err := ipPoolWebhook.ValidateUpdate(ctx, ipPoolT, newIPPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("appends IP range that do not pertains to 'spec.subnet' to 'spec.ips'", func() {
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(subnetT.Spec.IPs, "172.18.40.1-172.18.40.2")
+
+					newIPPoolT := ipPoolT.DeepCopy()
+					newIPPoolT.Spec.IPs = append(newIPPoolT.Spec.IPs, "172.18.41.10")
+
+					ctx := context.TODO()
+					err := ipPoolWebhook.ValidateUpdate(ctx, ipPoolT, newIPPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				PIt("failed to list IPPools due to some unknown errors", func() {})
+
+				PIt("failed to assemble the total IP addresses of the IPPool due to some unknown errors", func() {})
+
+				It("exists invalid IPPool in the cluster", func() {
+					existIPPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					existIPPoolT.Spec.Subnet = "172.18.40.0/24"
+					existIPPoolT.Spec.IPs = append(existIPPoolT.Spec.IPs, constant.InvalidIPRange)
+
+					ctx := context.TODO()
+					err := fakeClient.Create(ctx, existIPPoolT)
+					Expect(err).NotTo(HaveOccurred())
+
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs, "172.18.40.1-172.18.40.2")
+
+					newIPPoolT := ipPoolT.DeepCopy()
+					newIPPoolT.Spec.IPs = append(newIPPoolT.Spec.IPs, "172.18.40.10")
+
+					err = ipPoolWebhook.ValidateUpdate(ctx, ipPoolT, newIPPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("overlaps with existing IPPool", func() {
+					existIPPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					existIPPoolT.Spec.Subnet = "172.18.40.0/24"
+					existIPPoolT.Spec.IPs = append(existIPPoolT.Spec.IPs, "172.18.40.10")
+
+					ctx := context.TODO()
+					err := fakeClient.Create(ctx, existIPPoolT)
+					Expect(err).NotTo(HaveOccurred())
+
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs, "172.18.40.1-172.18.40.2")
+
+					newIPPoolT := ipPoolT.DeepCopy()
+					newIPPoolT.Spec.IPs = append(newIPPoolT.Spec.IPs, "172.18.40.10")
+
+					err = ipPoolWebhook.ValidateUpdate(ctx, ipPoolT, newIPPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+			})
+
+			When("Validating 'spec.excludeIPs'", func() {
+				It("appends invalid IP range to 'spec.excludeIPs'", func() {
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					newIPPoolT := ipPoolT.DeepCopy()
+					newIPPoolT.Spec.ExcludeIPs = append(newIPPoolT.Spec.ExcludeIPs, constant.InvalidIPRange)
+
+					ctx := context.TODO()
+					err := ipPoolWebhook.ValidateUpdate(ctx, ipPoolT, newIPPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("appends IP range that do not pertains to 'spec.subnet' to 'spec.excludeIPs'", func() {
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					newIPPoolT := ipPoolT.DeepCopy()
+					newIPPoolT.Spec.ExcludeIPs = append(newIPPoolT.Spec.ExcludeIPs, "172.18.41.10")
+
+					ctx := context.TODO()
+					err := ipPoolWebhook.ValidateUpdate(ctx, ipPoolT, newIPPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+			})
+
+			When("Validating 'spec.gateway'", func() {
+				It("updates 'spec.gateway' to invalid gateway", func() {
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+						[]string{
+							"172.18.40.2-172.18.40.3",
+							"172.18.40.10",
+						}...,
+					)
+					ipPoolT.Spec.Gateway = pointer.String("172.18.40.1")
+
+					newIPPoolT := ipPoolT.DeepCopy()
+					newIPPoolT.Spec.Gateway = pointer.String(constant.InvalidIP)
+
+					ctx := context.TODO()
+					err := ipPoolWebhook.ValidateUpdate(ctx, ipPoolT, newIPPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("updates 'spec.gateway' to a gateway that do not pertains to 'spec.subnet'", func() {
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+						[]string{
+							"172.18.40.2-172.18.40.3",
+							"172.18.40.10",
+						}...,
+					)
+					ipPoolT.Spec.Gateway = pointer.String("172.18.40.1")
+
+					newIPPoolT := ipPoolT.DeepCopy()
+					newIPPoolT.Spec.Gateway = pointer.String("172.18.41.1")
+
+					ctx := context.TODO()
+					err := ipPoolWebhook.ValidateUpdate(ctx, ipPoolT, newIPPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+			})
+
+			When("Validating 'spec.routes'", func() {
+				It("appends route with invalid destination", func() {
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+						[]string{
+							"172.18.40.2-172.18.40.3",
+							"172.18.40.10",
+						}...,
+					)
+
+					newIPPoolT := ipPoolT.DeepCopy()
+					newIPPoolT.Spec.Routes = append(newIPPoolT.Spec.Routes,
+						spiderpoolv1.Route{
+							Dst: constant.InvalidCIDR,
+							Gw:  "172.18.40.1",
+						},
+					)
+
+					ctx := context.TODO()
+					err := ipPoolWebhook.ValidateUpdate(ctx, ipPoolT, newIPPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("appends route with invalid gateway", func() {
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+						[]string{
+							"172.18.40.2-172.18.40.3",
+							"172.18.40.10",
+						}...,
+					)
+
+					newIPPoolT := ipPoolT.DeepCopy()
+					newIPPoolT.Spec.Routes = append(newIPPoolT.Spec.Routes,
+						spiderpoolv1.Route{
+							Dst: "192.168.40.0/24",
+							Gw:  constant.InvalidIP,
+						},
+					)
+
+					ctx := context.TODO()
+					err := ipPoolWebhook.ValidateUpdate(ctx, ipPoolT, newIPPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("appends route whose gateway does not pertains to 'spec.subnet'", func() {
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs,
+						[]string{
+							"172.18.40.2-172.18.40.3",
+							"172.18.40.10",
+						}...,
+					)
+
+					newIPPoolT := ipPoolT.DeepCopy()
+					newIPPoolT.Spec.Routes = append(newIPPoolT.Spec.Routes,
+						spiderpoolv1.Route{
+							Dst: "192.168.40.0/24",
+							Gw:  "172.18.41.1",
+						},
+					)
+
+					ctx := context.TODO()
+					err := ipPoolWebhook.ValidateUpdate(ctx, ipPoolT, newIPPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+			})
+
+			When("Validating the IP addresses being used", func() {
+				PIt("failed to assemble the total IP addresses of the IPPool due to some unknown errors", func() {})
+
+				It("removes IP range that is being used by IPPool", func() {
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(subnetT.Spec.IPs,
+						[]string{
+							"172.18.40.1-172.18.40.2",
+							"172.18.40.10",
+						}...,
+					)
+
+					ipPoolT.Status.AllocatedIPs = spiderpoolv1.PoolIPAllocations{
+						"172.18.40.10": spiderpoolv1.PoolIPAllocation{},
+					}
+
+					newIPPoolT := ipPoolT.DeepCopy()
+					newIPPoolT.Spec.IPs = newIPPoolT.Spec.IPs[:1]
+
+					ctx := context.TODO()
+					err := ipPoolWebhook.ValidateUpdate(ctx, ipPoolT, newIPPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+			})
+
+			When("Validating the existence of the controller Subnet", func() {
+				BeforeEach(func() {
+					ipPoolWebhook.EnableSpiderSubnet = true
+				})
+
+				It("updates orphan IPPool", func() {
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs, "172.18.40.1-172.18.40.2")
+
+					newIPPoolT := ipPoolT.DeepCopy()
+					newIPPoolT.Spec.IPs = append(newIPPoolT.Spec.IPs, "172.18.40.10")
+
+					ctx := context.TODO()
+					err := ipPoolWebhook.ValidateUpdate(ctx, ipPoolT, newIPPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("updates the IPPool that sets the owner reference to non-existent Subnet", func() {
+					subnetT.SetUID(uuid.NewUUID())
+					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 := controllerutil.SetControllerReference(subnetT, ipPoolT, scheme)
+					Expect(err).NotTo(HaveOccurred())
+
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs, "172.18.40.1-172.18.40.2")
+
+					newIPPoolT := ipPoolT.DeepCopy()
+					newIPPoolT.Spec.IPs = append(newIPPoolT.Spec.IPs, "172.18.40.10")
+
+					ctx := context.TODO()
+					err = ipPoolWebhook.ValidateUpdate(ctx, ipPoolT, newIPPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("updates the IPPool that sets the owner reference to a Subnet with different 'spec.subnet'", func() {
+					subnetT.SetUID(uuid.NewUUID())
+					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 = controllerutil.SetControllerReference(subnetT, ipPoolT, scheme)
+					Expect(err).NotTo(HaveOccurred())
+
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/25"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs, "172.18.40.1-172.18.40.2")
+
+					newIPPoolT := ipPoolT.DeepCopy()
+					newIPPoolT.Spec.IPs = append(newIPPoolT.Spec.IPs, "172.18.40.10")
+
+					err = ipPoolWebhook.ValidateUpdate(ctx, ipPoolT, newIPPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+
+				It("updates the IPPool that sets the owner reference to a terminating Subnet", func() {
+					controllerutil.AddFinalizer(subnetT, constant.SpiderFinalizer)
+					subnetT.SetUID(uuid.NewUUID())
+					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 = fakeClient.Delete(ctx, subnetT)
+					Expect(err).NotTo(HaveOccurred())
+
+					err = controllerutil.SetControllerReference(subnetT, ipPoolT, scheme)
+					Expect(err).NotTo(HaveOccurred())
+
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs, "172.18.40.1-172.18.40.2")
+
+					newIPPoolT := ipPoolT.DeepCopy()
+					newIPPoolT.Spec.IPs = append(newIPPoolT.Spec.IPs, "172.18.40.10")
+
+					err = ipPoolWebhook.ValidateUpdate(ctx, ipPoolT, newIPPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+			})
+
+			When("Validating the total IP addresses contained in the controller Subnet", func() {
+				BeforeEach(func() {
+					ipPoolWebhook.EnableSpiderSubnet = true
+				})
+
+				PIt("failed to assemble the total IP addresses of the IPPool due to some unknown errors", func() {})
+
+				PIt("failed to assemble the total IP addresses of the Subnet due to some unknown errors", func() {})
+
+				It("is out of the IP range of the Subnet", func() {
+					subnetT.SetUID(uuid.NewUUID())
+					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")
+
+					ctx := context.TODO()
+					err := fakeClient.Create(ctx, subnetT)
+					Expect(err).NotTo(HaveOccurred())
+
+					err = controllerutil.SetControllerReference(subnetT, ipPoolT, scheme)
+					Expect(err).NotTo(HaveOccurred())
+
+					ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+					ipPoolT.Spec.Subnet = "172.18.40.0/24"
+					ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs, "172.18.40.1-172.18.40.2")
+
+					newIPPoolT := ipPoolT.DeepCopy()
+					newIPPoolT.Spec.IPs = append(newIPPoolT.Spec.IPs, "172.18.40.10")
+
+					err = ipPoolWebhook.ValidateUpdate(ctx, ipPoolT, newIPPoolT)
+					Expect(apierrors.IsInvalid(err)).To(BeTrue())
+				})
+			})
+
+			It("deletes IPPool", func() {
+				controllerutil.AddFinalizer(ipPoolT, constant.SpiderFinalizer)
+				now := metav1.Now()
+				ipPoolT.SetDeletionTimestamp(&now)
+				ipPoolT.SetDeletionGracePeriodSeconds(pointer.Int64(0))
+
+				newIPPoolT := ipPoolT.DeepCopy()
+				controllerutil.RemoveFinalizer(newIPPoolT, constant.SpiderFinalizer)
+
+				ctx := context.TODO()
+				err := ipPoolWebhook.ValidateUpdate(ctx, ipPoolT, newIPPoolT)
+				Expect(err).NotTo(HaveOccurred())
+			})
+
+			It("updates terminating Subnet", func() {
+				controllerutil.AddFinalizer(ipPoolT, constant.SpiderFinalizer)
+				now := metav1.Now()
+				ipPoolT.SetDeletionTimestamp(&now)
+				ipPoolT.SetDeletionGracePeriodSeconds(pointer.Int64(30))
+
+				newIPPoolT := ipPoolT.DeepCopy()
+
+				ctx := context.TODO()
+				err := ipPoolWebhook.ValidateUpdate(ctx, ipPoolT, newIPPoolT)
+				Expect(apierrors.IsForbidden(err)).To(BeTrue())
+			})
+
+			It("updates IPv4 Subnet with all fields valid", func() {
+				ipPoolWebhook.EnableSpiderSubnet = true
+				subnetT.SetUID(uuid.NewUUID())
+				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",
+					}...,
+				)
+
+				ctx := context.TODO()
+				err := fakeClient.Create(ctx, subnetT)
+				Expect(err).NotTo(HaveOccurred())
+
+				err = controllerutil.SetControllerReference(subnetT, ipPoolT, scheme)
+				Expect(err).NotTo(HaveOccurred())
+
+				ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv4)
+				ipPoolT.Spec.Subnet = "172.18.40.0/24"
+				ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs, "172.18.40.2-172.18.40.3")
+				ipPoolT.Spec.Vlan = pointer.Int64(0)
+
+				newIPPoolT := ipPoolT.DeepCopy()
+				newIPPoolT.Spec.IPs = append(newIPPoolT.Spec.IPs, "172.18.40.10")
+				newIPPoolT.Spec.ExcludeIPs = append(newIPPoolT.Spec.ExcludeIPs, "172.18.40.10")
+				newIPPoolT.Spec.Gateway = pointer.String("172.18.40.1")
+				newIPPoolT.Spec.Routes = append(newIPPoolT.Spec.Routes,
+					spiderpoolv1.Route{
+						Dst: "192.168.40.0/24",
+						Gw:  "172.18.40.40",
+					},
+				)
+
+				err = ipPoolWebhook.ValidateUpdate(ctx, ipPoolT, newIPPoolT)
+				Expect(err).NotTo(HaveOccurred())
+			})
+
+			It("updates IPv6 Subnet with all fields valid", func() {
+				ipPoolWebhook.EnableSpiderSubnet = true
+				subnetT.SetUID(uuid.NewUUID())
+				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",
+					}...,
+				)
+
+				ctx := context.TODO()
+				err := fakeClient.Create(ctx, subnetT)
+				Expect(err).NotTo(HaveOccurred())
+
+				err = controllerutil.SetControllerReference(subnetT, ipPoolT, scheme)
+				Expect(err).NotTo(HaveOccurred())
+
+				ipPoolT.Spec.IPVersion = pointer.Int64(constant.IPv6)
+				ipPoolT.Spec.Subnet = "abcd:1234::/120"
+				ipPoolT.Spec.IPs = append(ipPoolT.Spec.IPs, "abcd:1234::2-abcd:1234::3")
+				ipPoolT.Spec.Vlan = pointer.Int64(0)
+
+				newIPPoolT := ipPoolT.DeepCopy()
+				newIPPoolT.Spec.IPs = append(newIPPoolT.Spec.IPs, "abcd:1234::a")
+				newIPPoolT.Spec.ExcludeIPs = append(newIPPoolT.Spec.ExcludeIPs, "abcd:1234::a")
+				newIPPoolT.Spec.Gateway = pointer.String("abcd:1234::1")
+				newIPPoolT.Spec.Routes = append(newIPPoolT.Spec.Routes,
+					spiderpoolv1.Route{
+						Dst: "fd00:40::/120",
+						Gw:  "abcd:1234::28",
+					},
+				)
+
+				err = ipPoolWebhook.ValidateUpdate(ctx, ipPoolT, newIPPoolT)
+				Expect(err).NotTo(HaveOccurred())
+			})
+		})
+
+		Describe("ValidateDelete", func() {
+			It("passes", func() {
+				ctx := context.TODO()
+				err := ipPoolWebhook.ValidateDelete(ctx, ipPoolT)
+				Expect(err).NotTo(HaveOccurred())
+			})
+		})
+	})
+})