Repository: spidernet-io/spiderpool
Pull request #1097 (2022-11-25 08:21:48)

test: Add unit tests for pkg ip
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=1097)

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

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


weizhoublue: fix the conflicts
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=1097)

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

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



Patch:
diff --git a/.github/workflows/auto-cherrypick.yaml b/.github/workflows/auto-cherrypick.yaml
deleted file mode 100644
index 1999cf327..000000000
--- a/.github/workflows/auto-cherrypick.yaml
+++ /dev/null
@@ -1,165 +0,0 @@
-name: Auto CherryPick PR
-
-permissions: write-all
-
-env:
-  PR_LABEL_PREFIX_CHERRYPICK: "cherrypick-"
-  CHERRYPICK_LABEL: "robot-cherrypick"
-  DEFAULT_REVIEWER: "weizhoublue"
-
-on:
-  push:
-    branches:
-      - 'release-*'
-      - 'main'
-
-jobs:
-  cherry_pick:
-    runs-on: ubuntu-latest
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v3
-        with:
-          fetch-depth: 0
-
-      - name: cherry pick
-        run: |
-          set -x
-          echo ${{ secrets.WELAN_PAT }} | gh auth login --with-token
-          DATA_FILE="/tmp/`date +%Y%m%d%H%M%S%N`"
-          echo '${{ toJSON(github) }}' > ${DATA_FILE}
-          echo "============ print var ======== "
-              grep -Eio "Merge pull request #[0-9]+ " < ${DATA_FILE}
-          PR_NUMBER=` cat ${DATA_FILE} | grep -Eio "Merge pull request #[0-9]+ " | grep -Eo "[0-9]+" | uniq `
-              [ -n "${PR_NUMBER}" ] || { echo "no PR number, ignore" ; }
-          PR_URL="https://github.com/${{ github.repository }}/pull/${PR_NUMBER}"
-          ACTION_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
-              grep '"username":'  < ${DATA_FILE}
-          PR_AUTHOR=` cat ${DATA_FILE} | grep '"username":'  | awk -F'"' '{print $4}' | sort | uniq  | grep -v "web-flow"  | head -1 | tr -d '\n' `
-              [ -n "${PR_AUTHOR}" ] || { echo "no PR_AUTHOR, ignore" ; }
-          gh pr view ${PR_NUMBER}
-          PR_LABEL=` gh pr view ${PR_NUMBER} | grep -i "^labels:" | tr ',' ' ' | tr -s ' ' | sed 's/labels://g' `
-              [ -n "${PR_AUTHOR}" ] || { echo "no PR_LABEL, ignore" ; }
-          #
-              cat ${DATA_FILE} | grep -Ei "https://github.com/.*/commit"
-          PR_COMMITS=` cat ${DATA_FILE} | grep -Ei "https://github.com/.*/commit" | awk -F'"' '{print $4}' | uniq -c | awk '{ if ($1 == 1 ) print $2}' | awk -F'/' '{print $NF}' | tr '\n' ' '  `
-              [ -n "${PR_COMMITS}" ] || { echo "no PR_COMMITS, ignore" ; }
-          #
-          echo "number: ${PR_NUMBER}"
-          echo "action url: ${ACTION_URL}"
-          echo "PR_COMMITS: ${PR_COMMITS}"
-          echo "author: ${PR_AUTHOR}"
-          echo "url: ${PR_URL}"
-          echo "PR_LABEL: ${PR_LABEL}"
-          #
-          #
-          echo "=============================== get dest branch from labels ======== "
-          WANT_MERGE_BRANCH_LIST=""
-          for LABEL in ${PR_LABEL} ; do
-              echo "checking label $LABEL "
-              PREFIX="${{ env.PR_LABEL_PREFIX_CHERRYPICK }}"
-              grep -E "^${PREFIX}" <<< "${LABEL}" &>/dev/null || continue
-              BRANCH_NAME=` sed 's?'"${PREFIX}"'??' <<< "$LABEL" `
-              WANT_MERGE_BRANCH_LIST+=" $BRANCH_NAME "
-          done
-          [ -z "$WANT_MERGE_BRANCH_LIST" ] && echo "no branch to cherry pick" && exit 0
-          echo "cherry pick to $WANT_MERGE_BRANCH_LIST "
-          #
-          #
-          echo "============ begin to cherry pick ============ "
-          FINAL_FAILURE=false
-          git branch
-          git config  user.email "robot@example.com"
-          git config  user.name "robot"
-          gh label create ${{ env.CHERRYPICK_LABEL }} || true
-          for BRANCH in $WANT_MERGE_BRANCH_LIST ; do
-              echo "************** cherry for branch ${BRANCH}"
-              if ! git ls-remote --exit-code --heads origin ${BRANCH} ; then
-                  # todo: create issue
-                  echo "error, branch $BRANCH does not exist"
-                  gh issue create  \
-                      --body "reason: the branch $BRANCH does not exist. PR <${PR_URL}> ,  action <${ACTION_URL}> " \
-                      --title "failed to auto cherry pick PR ${PR_NUMBER} to branch ${BRANCH}" \
-                      --label "${{ env.CHERRYPICK_LABEL }}" \
-                      --assignee "${PR_AUTHOR},${{ env.DEFAULT_REVIEWER }}"
-                  if (($?!=0)) ; then
-                      echo "!!!! error, failed to create issue"
-                      FINAL_FAILURE=true
-                  fi
-                  continue
-              fi
-              git fetch origin ${BRANCH}:${BRANCH} || true
-              if ! git checkout ${BRANCH} ; then
-                  echo "error, failed to checkout to branch $BRANCH"
-                  gh issue create  \
-                      --body "reason: failed to get the branch $BRANCH. PR <${PR_URL}> ,  action <${ACTION_URL}> " \
-                      --title "failed to auto cherry pick PR ${PR_NUMBER} to branch ${BRANCH}" \
-                      --label "${{ env.CHERRYPICK_LABEL }}" \
-                      --assignee "${PR_AUTHOR},${{ env.DEFAULT_REVIEWER }}"
-                  if (($?!=0)) ; then
-                      echo "!!!! error, failed to create issue"
-                      FINAL_FAILURE=true
-                  fi
-                  continue
-              fi
-              PR_BRANCH=robot/cherrypick/pr${PR_NUMBER}/${BRANCH}
-              git checkout -b ${PR_BRANCH}
-              git branch --show-current
-              FAIL=false
-              UPDATE=false
-              ERROR_MESSAGE=""
-              for COMMIT in $PR_COMMITS; do
-                    if ! ERROR_MESSAGE=`git cherry-pick $COMMIT 2>&1` ; then
-                          echo ">>>>>>>>>>>> fail when cherry pick $COMMIT to branch $BRANCH "
-                          echo "$ERROR_MESSAGE"
-                          echo "---- failuire detail"
-                          git status
-                          git diff
-                          echo "<<<<<<<<<<<<<"
-                          if git diff --exit-code --quiet ; then
-                              echo "no changes happen, ignore cherry pick $COMMIT "
-                              git cherry-pick --abort || true
-                              git reset --hard HEAD || true
-                              continue
-                          else
-                              FAIL=true
-                              echo "error, failed to cherry pick $COMMIT "
-                              git cherry-pick --abort || true
-                              git reset --hard HEAD || true
-                              break
-                          fi
-                    else
-                        UPDATE=true
-                        echo "succeed to cherry pick $COMMIT to branch $BRANCH "
-                    fi
-              done
-              if [ "$FAIL" == "true" ] ; then
-                  echo "failed to cherry pick to branch $BRANCH "
-                  FINAL_FAILURE=true
-                  echo -e "commits $PR_COMMITS conflict when merging to branch $BRANCH, please manually cherry pick it by yourself. \n PR <${PR_URL}> ,  action <${ACTION_URL}>  \n\n  \`\`\`\n${ERROR_MESSAGE}\n\`\`\` " | \
-                       gh issue create  \
-                        --title "failed to auto cherry pick PR ${PR_NUMBER} to branch ${BRANCH}" \
-                        --label "${{ env.CHERRYPICK_LABEL }}" \
-                        --assignee "${PR_AUTHOR},${{ env.DEFAULT_REVIEWER }}" \
-                        --body-file -
-              fi
-              if [ "$UPDATE" == "true" ] ; then
-                  echo "succeeded to cherry pick to branch $BRANCH "
-                  # create a pr
-                  git push origin ${PR_BRANCH}:${PR_BRANCH} -f
-                  gh pr create --title "auto cherry pick PR ${PR_NUMBER} to branch ${BRANCH}" \
-                      --assignee "${PR_AUTHOR},${{ env.DEFAULT_REVIEWER }}" \
-                      --label ${{ env.CHERRYPICK_LABEL }} \
-                      --body "robot cherry pick pr <${PR_URL}>,  action <${ACTION_URL}>  , commits $PR_COMMITS " \
-                      --base ${BRANCH}
-              else
-                  echo "no changes happened for commits $PR_COMMITS, ignore create pr"
-              fi
-          done
-          echo "=========== check result ============"
-          if [ "$FINAL_FAILURE" == "true" ] ; then
-              # if failed to create issue or pr, fails
-              echo "error, failuire happened"
-          else
-              echo "all done"
-          fi
diff --git a/.github/workflows/auto-version-release.yaml b/.github/workflows/auto-version-release.yaml
index 0213833d3..331d412d6 100644
--- a/.github/workflows/auto-version-release.yaml
+++ b/.github/workflows/auto-version-release.yaml
@@ -25,40 +25,30 @@ jobs:
   get-tag:
     runs-on: ubuntu-latest
     outputs:
-      tag: ${{ env.RUN_TAG }}
+      tag: ${{ steps.get_ref.outputs.tag }}
     steps:
       - name: Get Ref
         id: get_ref
         run: |
           if ${{ github.event_name == 'workflow_dispatch' }} ; then
             echo "call by self workflow_dispatch"
-            echo "RUN_TAG=${{ github.event.inputs.tag }}" >> $GITHUB_ENV
-            YBranchName=` grep -Eo "v[0-9]+\.[0-9]+" <<< "${{ github.event.inputs.tag }}" `
+            echo ::set-output name=tag::${{ github.event.inputs.tag }}
           elif ${{ github.event_name == 'push' }} ; then
             echo "call by push tag"
-            echo "RUN_TAG=${GITHUB_REF##*/}" >> $GITHUB_ENV
-            YBranchName=` grep -Eo "v[0-9]+\.[0-9]+" <<< "${GITHUB_REF##*/}" `
+            echo ::set-output name=tag::${GITHUB_REF##*/}
           else
             echo "unexpected event: ${{ github.event_name }}"
             exit 1
           fi
-          echo "YBranchName=${YBranchName}"
-          if [ -n "$YBranchName" ] ; then
-              echo "RUN_YBranchName=${YBranchName}" >> $GITHUB_ENV
-          else
-              echo "error, failed to find y branch"
-              exit 1
-          fi
-
 
       - name: Checkout
         uses: actions/checkout@v3
         with:
-          ref: ${{ env.RUN_TAG }}
+          ref: ${{ steps.get_ref.outputs.tag }}
 
       - name: Check Version
         run: |
-          TagVersion="${{ env.RUN_TAG }}"
+          TagVersion="${{ steps.get_ref.outputs.tag }}"
           RecordVersion=` cat VERSION  | tr -d ' ' | tr -d '\n' `
           if [ "$RecordVersion" != "$TagVersion" ] ; then
             echo "error, version $RecordVersion of '/VERSION' is different with Tag $TagVersion "
@@ -66,28 +56,14 @@ jobs:
           fi
           #no need to check chart version, which will auto update to /VERSION by CI
 
-      # if branch exists, the action will no fail, and it output created=false
-      - name: release Y branch
+      - name: Create branch
         uses: peterjgrainger/action-create-branch@v2.3.0
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
         with:
-          branch: 'release-${{ env.RUN_YBranchName }}'
+          branch: 'release-${{ steps.get_ref.outputs.tag }}'
           sha: '${{ github.sha }}'
 
-      # for auto-cherrypick.yaml, create a label
-      - name: Create cherry-pick label
-        continue-on-error: true
-        run: |
-          echo ${{ secrets.WELAN_PAT }} | gh auth login --with-token
-          branchLabelName="${{ env.LABEL_PREFIX_CHERRYPICK }}release-${{ env.RUN_YBranchName }}"
-          mainLabelName="${{ env.LABEL_PREFIX_CHERRYPICK }}main"
-          echo "try to create label "
-          gh label create ${branchLabelName} --force
-          (( $? !=0 )) && echo "error, failed to create label ${branchLabelName}"
-          gh label create ${mainLabelName} --force
-          (( $? !=0 )) && echo "error, failed to create label ${mainLabelName}"
-
   build-release-image:
     needs: get-tag
     uses: ./.github/workflows/call-release-image.yaml
diff --git a/.github/workflows/call-update-githubpages.yaml b/.github/workflows/call-update-githubpages.yaml
index be73013c7..455072f40 100644
--- a/.github/workflows/call-update-githubpages.yaml
+++ b/.github/workflows/call-update-githubpages.yaml
@@ -76,7 +76,7 @@ jobs:
           helm repo index  ./charts  --url ${{ steps.get_base_url.outputs.url }}/charts
           mv ./charts/index.yaml ./index.yaml
 
-      # ========= finally, create PR
+      #========= finally, create PR
       # Allow auto-merge on general
       - name: Create Pull Request
         id: create_pr
diff --git a/pkg/ip/ip.go b/pkg/ip/ip.go
index a37948110..1d576c227 100644
--- a/pkg/ip/ip.go
+++ b/pkg/ip/ip.go
@@ -4,11 +4,14 @@
 package ip
 
 import (
+	"bytes"
 	"fmt"
 	"math/big"
 	"net"
+	"sort"
 
 	"github.com/asaskevich/govalidator"
+
 	"github.com/spidernet-io/spiderpool/pkg/constant"
 	"github.com/spidernet-io/spiderpool/pkg/types"
 )
@@ -35,7 +38,12 @@ func ParseIP(version types.IPVersion, s string, isCIDR bool) (*net.IPNet, error)
 		if err := IsIP(version, s); err != nil {
 			return nil, err
 		}
-		return &net.IPNet{IP: net.ParseIP(s)}, nil
+
+		if version == constant.IPv4 {
+			return &net.IPNet{IP: net.ParseIP(s), Mask: net.CIDRMask(32, 32)}, nil
+		}
+
+		return &net.IPNet{IP: net.ParseIP(s), Mask: net.CIDRMask(128, 128)}, nil
 	}
 }
 
@@ -91,6 +99,10 @@ func IPsDiffSet(ips1, ips2 []net.IP) []net.IP {
 		ips = append(ips, net.ParseIP(k))
 	}
 
+	sort.Slice(ips, func(i, j int) bool {
+		return bytes.Compare(ips[i].To16(), ips[j].To16()) < 0
+	})
+
 	return ips
 }
 
@@ -111,6 +123,10 @@ func IPsUnionSet(ips1, ips2 []net.IP) []net.IP {
 		ips = append(ips, net.ParseIP(k))
 	}
 
+	sort.Slice(ips, func(i, j int) bool {
+		return bytes.Compare(ips[i].To16(), ips[j].To16()) < 0
+	})
+
 	return ips
 }
 
@@ -137,6 +153,10 @@ func IPsIntersectionSet(ips1, ips2 []net.IP) []net.IP {
 		ips = append(ips, net.ParseIP(k))
 	}
 
+	sort.Slice(ips, func(i, j int) bool {
+		return bytes.Compare(ips[i].To16(), ips[j].To16()) < 0
+	})
+
 	return ips
 }
 
@@ -172,5 +192,5 @@ func ipToInt(ip net.IP) *big.Int {
 
 // intToIP converts big.Int to net.IP.
 func intToIP(i *big.Int) net.IP {
-	return net.IP(i.Bytes())
+	return net.IP(i.Bytes()).To16()
 }
diff --git a/pkg/ip/iprange.go b/pkg/ip/iprange.go
index aa3c6ba97..905717889 100644
--- a/pkg/ip/iprange.go
+++ b/pkg/ip/iprange.go
@@ -17,8 +17,8 @@ import (
 )
 
 // MergeIPRanges merges dispersed IP ranges.
-// For example, transport [192.168.0.1-192.168.0.3, 192.168.0.2-192.168.0.5]
-// to [192.168.0.1-192.168.0.5]. The overlapping part of two IP ranges will
+// For example, transport [172.18.40.1-172.18.40.3, 172.18.40.2-172.18.40.5]
+// to [172.18.40.1-172.18.40.5]. The overlapping part of two IP ranges will
 // be ignored.
 func MergeIPRanges(version types.IPVersion, ipRanges []string) ([]string, error) {
 	ips, err := ParseIPRanges(version, ipRanges)
@@ -166,14 +166,14 @@ func IsIPRangeOverlap(version types.IPVersion, ipRange1, ipRange2 string) (bool,
 }
 
 // IsIPRange reports whether ipRange string is a valid IP range. An IP
-// range can be a single IP address in the style of '192.168.1.0', or
-// an address range in the form of '192.168.1.0-192.168.1.10'.
+// range can be a single IP address in the style of '172.18.40.0', or
+// an address range in the form of '172.18.40.0-172.18.40.10'.
 // The following formats are invalid:
-// "192.168.1.0 - 192.168.1.10": there can be no space between two IP
+// "172.18.40.0 - 172.18.40.10": there can be no space between two IP
 // addresses.
-// "192.168.1.1-2001:db8:a0b:12f0::1": invalid combination of IPv4 and
+// "172.18.40.1-2001:db8:a0b:12f0::1": invalid combination of IPv4 and
 // IPv6.
-// "192.168.1.10-192.168.1.1": the IP range must be ordered.
+// "172.18.40.10-172.18.40.1": the IP range must be ordered.
 func IsIPRange(version types.IPVersion, ipRange string) error {
 	if err := IsIPVersion(version); err != nil {
 		return err
diff --git a/pkg/ip/utils.go b/pkg/ip/utils.go
index d27de05fe..9bb659a04 100644
--- a/pkg/ip/utils.go
+++ b/pkg/ip/utils.go
@@ -9,12 +9,12 @@ import (
 	"github.com/spidernet-io/spiderpool/pkg/types"
 )
 
-func AssembleTotalIPs(ipVersion types.IPVersion, ipRanges, excludeIPRanges []string) ([]net.IP, error) {
+func AssembleTotalIPs(ipVersion types.IPVersion, ipRanges, excludedIPRanges []string) ([]net.IP, error) {
 	ips, err := ParseIPRanges(ipVersion, ipRanges)
 	if nil != err {
 		return nil, err
 	}
-	excludeIPs, err := ParseIPRanges(ipVersion, excludeIPRanges)
+	excludeIPs, err := ParseIPRanges(ipVersion, excludedIPRanges)
 	if nil != err {
 		return nil, err
 	}
diff --git a/pkg/ippoolmanager/ippool_informer.go b/pkg/ippoolmanager/ippool_informer.go
index 977f1c2fd..81b431ebe 100644
--- a/pkg/ippoolmanager/ippool_informer.go
+++ b/pkg/ippoolmanager/ippool_informer.go
@@ -4,12 +4,10 @@
 package ippoolmanager
 
 import (
-	"bytes"
 	"context"
 	"errors"
 	"fmt"
 	"reflect"
-	"sort"
 	"time"
 
 	"go.uber.org/zap"
@@ -456,10 +454,6 @@ func (c *poolInformerController) scaleIPPoolIfNeeded(ctx context.Context, pool *
 				return fmt.Errorf("%w: failed to parse IP ranges '%v', error: %v", constant.ErrWrongInput, allocatedIPRanges, err)
 			}
 			freeIPs := spiderpoolip.IPsDiffSet(totalIPs, allocatedIPs)
-			// sort freeIPs
-			sort.Slice(freeIPs, func(i, j int) bool {
-				return bytes.Compare(freeIPs[i].To16(), freeIPs[j].To16()) < 0
-			})
 			discardedIPs := freeIPs[:totalIPCount-desiredIPNum]
 			discardedIPRanges, err := spiderpoolip.ConvertIPsToIPRanges(*pool.Spec.IPVersion, discardedIPs)
 			if nil != err {
diff --git a/pkg/subnetmanager/subnet_manager.go b/pkg/subnetmanager/subnet_manager.go
index 767ad449a..b8d6d7e00 100644
--- a/pkg/subnetmanager/subnet_manager.go
+++ b/pkg/subnetmanager/subnet_manager.go
@@ -4,12 +4,10 @@
 package subnetmanager
 
 import (
-	"bytes"
 	"context"
 	"errors"
 	"fmt"
 	"net"
-	"sort"
 
 	"go.uber.org/zap"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -142,9 +140,6 @@ func (sm *subnetManager) GenerateIPsFromSubnetWhenScaleUpIP(ctx context.Context,
 			} else if len(lastAllocatedIPs) > desiredIPNum-len(poolTotalIPs) {
 				// last allocated IPs is greater than the current allocation request,
 				// we will update the SpiderSubnet status correctly in next IPPool webhook SpiderSubnet update procession
-				sort.Slice(lastAllocatedIPs, func(i, j int) bool {
-					return bytes.Compare(lastAllocatedIPs[i].To16(), lastAllocatedIPs[j].To16()) < 0
-				})
 				return spiderpoolip.ConvertIPsToIPRanges(ipVersion, lastAllocatedIPs[:desiredIPNum-len(poolTotalIPs)])
 			} else {
 				// last allocated IPs less than the current allocation request,
@@ -189,11 +184,6 @@ func (sm *subnetManager) GenerateIPsFromSubnetWhenScaleUpIP(ctx context.Context,
 		return nil, fmt.Errorf("insufficient subnet FreeIPs, required '%d' but only left '%d'", ipNum, len(freeIPs))
 	}
 
-	// sort freeIPs
-	sort.Slice(freeIPs, func(i, j int) bool {
-		return bytes.Compare(freeIPs[i].To16(), freeIPs[j].To16()) < 0
-	})
-
 	allocateIPs := make([]net.IP, 0, ipNum)
 	if cursor {
 		allocateIPs = append(allocateIPs, freeIPs[:ipNum]...)
diff --git a/pkg/types/ip.go b/pkg/types/ip.go
index efd85610f..459806e59 100644
--- a/pkg/types/ip.go
+++ b/pkg/types/ip.go
@@ -6,3 +6,8 @@ package types
 type IPVersion = int64
 
 type Vlan = int64
+
+type IPAndCID struct {
+	IP          string
+	ContainerID string
+}
diff --git a/pkg/types/types.go b/pkg/types/types.go
deleted file mode 100644
index 5df11fca0..000000000
--- a/pkg/types/types.go
+++ /dev/null
@@ -1,9 +0,0 @@
-// Copyright 2022 Authors of spidernet-io
-// SPDX-License-Identifier: Apache-2.0
-
-package types
-
-type IPAndCID struct {
-	IP          string
-	ContainerID string
-}


Test patch:
diff --git a/pkg/ip/cidr_test.go b/pkg/ip/cidr_test.go
new file mode 100644
index 000000000..dcf209b7a
--- /dev/null
+++ b/pkg/ip/cidr_test.go
@@ -0,0 +1,186 @@
+// Copyright 2022 Authors of spidernet-io
+// SPDX-License-Identifier: Apache-2.0
+
+package ip_test
+
+import (
+	"net"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+
+	"github.com/spidernet-io/spiderpool/pkg/constant"
+	spiderpoolip "github.com/spidernet-io/spiderpool/pkg/ip"
+)
+
+var _ = Describe("CIDR", Label("cidr_test"), func() {
+	Describe("Test ParseCIDR", func() {
+		When("Verifying", func() {
+			It("inputs invalid IP version", func() {
+				ip, err := spiderpoolip.ParseCIDR(invalidIPVersion, "172.18.40.40/24")
+				Expect(err).To(MatchError(spiderpoolip.ErrInvalidIPVersion))
+				Expect(ip).To(BeNil())
+			})
+
+			It("inputs invalid CIDR address", func() {
+				ip, err := spiderpoolip.ParseCIDR(constant.IPv4, invalidCIDR)
+				Expect(err).To(MatchError(spiderpoolip.ErrInvalidCIDRFormat))
+				Expect(ip).To(BeNil())
+			})
+		})
+
+		It("parses IPv4 CIDR address", func() {
+			ipNet, err := spiderpoolip.ParseCIDR(constant.IPv4, "172.18.40.40/24")
+			Expect(err).NotTo(HaveOccurred())
+			Expect(ipNet).To(Equal(
+				&net.IPNet{
+					IP:   net.IPv4(172, 18, 40, 0).To4(),
+					Mask: net.CIDRMask(24, 32),
+				},
+			))
+		})
+
+		It("parses IPv6 CIDR address", func() {
+			ipNet, err := spiderpoolip.ParseCIDR(constant.IPv6, "abcd:1234::1/120")
+			Expect(err).NotTo(HaveOccurred())
+			Expect(ipNet).To(Equal(
+				&net.IPNet{
+					IP:   net.ParseIP("abcd:1234::0"),
+					Mask: net.CIDRMask(120, 128),
+				},
+			))
+		})
+	})
+
+	Describe("Test ContainsCIDR", func() {
+		When("Verifying", func() {
+			It("inputs invalid IP version", func() {
+				contains, err := spiderpoolip.ContainsCIDR(invalidIPVersion, "172.18.40.0/24", "172.18.40.0/25")
+				Expect(err).To(MatchError(spiderpoolip.ErrInvalidIPVersion))
+				Expect(contains).To(BeFalse())
+			})
+
+			It("inputs invalid CIDR address", func() {
+				contains, err := spiderpoolip.ContainsCIDR(constant.IPv4, invalidCIDR, "172.18.40.0/25")
+				Expect(err).To(MatchError(spiderpoolip.ErrInvalidCIDRFormat))
+				Expect(contains).To(BeFalse())
+
+				contains, err = spiderpoolip.ContainsCIDR(constant.IPv4, "172.18.40.0/24", invalidCIDR)
+				Expect(err).To(MatchError(spiderpoolip.ErrInvalidCIDRFormat))
+				Expect(contains).To(BeFalse())
+			})
+		})
+
+		When("IPv4", func() {
+			It("tests that a CIDR address contains another CIDR address", func() {
+				contains, err := spiderpoolip.ContainsCIDR(constant.IPv4, "172.18.40.0/24", "172.18.40.0/25")
+				Expect(err).NotTo(HaveOccurred())
+				Expect(contains).To(BeTrue())
+			})
+
+			It("tests that one CIDR address does not contain another CIDR address", func() {
+				contains, err := spiderpoolip.ContainsCIDR(constant.IPv4, "172.18.40.0/25", "172.18.40.0/24")
+				Expect(err).NotTo(HaveOccurred())
+				Expect(contains).To(BeFalse())
+			})
+		})
+
+		When("IPv6", func() {
+			It("tests that a CIDR address contains another CIDR address", func() {
+				contains, err := spiderpoolip.ContainsCIDR(constant.IPv6, "abcd:1234::/120", "abcd:1234::/121")
+				Expect(err).NotTo(HaveOccurred())
+				Expect(contains).To(BeTrue())
+			})
+
+			It("tests that one CIDR address does not contain another CIDR address", func() {
+				contains, err := spiderpoolip.ContainsCIDR(constant.IPv6, "abcd:1234::/121", "abcd:1234::/120")
+				Expect(err).NotTo(HaveOccurred())
+				Expect(contains).To(BeFalse())
+			})
+		})
+	})
+
+	Describe("Test IsCIDROverlap", func() {
+		When("Verifying", func() {
+			It("inputs invalid IP version", func() {
+				overlap, err := spiderpoolip.IsCIDROverlap(invalidIPVersion, "172.18.40.0/24", "172.18.40.0/25")
+				Expect(err).To(MatchError(spiderpoolip.ErrInvalidIPVersion))
+				Expect(overlap).To(BeFalse())
+			})
+
+			It("inputs invalid CIDR address", func() {
+				overlap, err := spiderpoolip.IsCIDROverlap(constant.IPv4, invalidCIDR, "172.18.40.0/25")
+				Expect(err).To(MatchError(spiderpoolip.ErrInvalidCIDRFormat))
+				Expect(overlap).To(BeFalse())
+
+				overlap, err = spiderpoolip.IsCIDROverlap(constant.IPv4, "172.18.40.0/24", invalidCIDR)
+				Expect(err).To(MatchError(spiderpoolip.ErrInvalidCIDRFormat))
+				Expect(overlap).To(BeFalse())
+			})
+		})
+
+		When("IPv4", func() {
+			It("tests that two CIDR addresses overlap", func() {
+				overlap, err := spiderpoolip.IsCIDROverlap(constant.IPv4, "172.18.40.0/24", "172.18.40.0/25")
+				Expect(err).NotTo(HaveOccurred())
+				Expect(overlap).To(BeTrue())
+			})
+
+			It("tests that two CIDR addresses do not overlap", func() {
+				overlap, err := spiderpoolip.IsCIDROverlap(constant.IPv4, "172.18.41.0/24", "172.18.40.0/24")
+				Expect(err).NotTo(HaveOccurred())
+				Expect(overlap).To(BeFalse())
+			})
+		})
+
+		When("IPv6", func() {
+			It("tests that two CIDR addresses overlap", func() {
+				overlap, err := spiderpoolip.IsCIDROverlap(constant.IPv6, "abcd:1234::/120", "abcd:1234::/121")
+				Expect(err).NotTo(HaveOccurred())
+				Expect(overlap).To(BeTrue())
+			})
+
+			It("tests that two CIDR addresses do not overlap", func() {
+				overlap, err := spiderpoolip.IsCIDROverlap(constant.IPv6, "abcd:1235::/120", "abcd:1234::/120")
+				Expect(err).NotTo(HaveOccurred())
+				Expect(overlap).To(BeFalse())
+			})
+		})
+	})
+
+	Describe("Test IsCIDR", func() {
+		When("Verifying", func() {
+			It("inputs invalid IP version", func() {
+				err := spiderpoolip.IsCIDR(invalidIPVersion, "172.18.40.0/24")
+				Expect(err).To(MatchError(spiderpoolip.ErrInvalidIPVersion))
+			})
+
+			It("inputs invalid CIDR address", func() {
+				err := spiderpoolip.IsCIDR(constant.IPv4, invalidCIDR)
+				Expect(err).To(MatchError(spiderpoolip.ErrInvalidCIDRFormat))
+			})
+		})
+
+		It("is an IPv4 CIDR address", func() {
+			Expect(spiderpoolip.IsCIDR(constant.IPv4, "172.18.40.0/24")).To(Succeed())
+		})
+
+		It("is an IPv6 CIDR address", func() {
+			Expect(spiderpoolip.IsCIDR(constant.IPv6, "abcd:1234::/120")).To(Succeed())
+		})
+	})
+
+	Describe("Test IsIPv4CIDR", func() {
+		It("tests whether it is an IPv4 CIDR address", func() {
+			Expect(spiderpoolip.IsIPv4CIDR(invalidCIDR)).To(BeFalse())
+			Expect(spiderpoolip.IsIPv4CIDR("172.18.40.0/24")).To(BeTrue())
+		})
+	})
+
+	Describe("Test IsIPv6CIDR", func() {
+		It("tests whether it is an IPv6 CIDR address", func() {
+			Expect(spiderpoolip.IsIPv6CIDR(invalidCIDR)).To(BeFalse())
+			Expect(spiderpoolip.IsIPv6CIDR("abcd:1234::/120")).To(BeTrue())
+		})
+	})
+})
diff --git a/pkg/ip/ip_suite_test.go b/pkg/ip/ip_suite_test.go
new file mode 100644
index 000000000..c562108d4
--- /dev/null
+++ b/pkg/ip/ip_suite_test.go
@@ -0,0 +1,29 @@
+// Copyright 2022 Authors of spidernet-io
+// SPDX-License-Identifier: Apache-2.0
+
+package ip_test
+
+import (
+	"testing"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+
+	"github.com/spidernet-io/spiderpool/pkg/types"
+)
+
+const (
+	invalidIPVersion = types.IPVersion(976)
+	invalidCIDR      = "invalid CIDR"
+	invalidIP        = "invalid IP"
+	invalidIPRange   = "invalid IP range"
+	invalidDst       = "invalid routing destination"
+	invalidGateway   = "invalid routing gateway"
+)
+
+var invalidIPRanges = []string{invalidIPRange}
+
+func TestIP(t *testing.T) {
+	RegisterFailHandler(Fail)
+	RunSpecs(t, "IP Suite", Label("ip", "unitest"))
+}
diff --git a/pkg/ip/ip_test.go b/pkg/ip/ip_test.go
new file mode 100644
index 000000000..2c3aeb477
--- /dev/null
+++ b/pkg/ip/ip_test.go
@@ -0,0 +1,343 @@
+// Copyright 2022 Authors of spidernet-io
+// SPDX-License-Identifier: Apache-2.0
+
+package ip_test
+
+import (
+	"net"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+
+	"github.com/spidernet-io/spiderpool/pkg/constant"
+	spiderpoolip "github.com/spidernet-io/spiderpool/pkg/ip"
+)
+
+var _ = Describe("IP", Label("ip_test"), func() {
+	Describe("Test IsIPVersion", func() {
+		It("inputs invalid IP version", func() {
+			err := spiderpoolip.IsIPVersion(invalidIPVersion)
+			Expect(err).To(MatchError(spiderpoolip.ErrInvalidIPVersion))
+		})
+
+		It("is IPv4", func() {
+			Expect(spiderpoolip.IsIPVersion(constant.IPv4)).To(Succeed())
+		})
+
+		It("is IPv6", func() {
+			Expect(spiderpoolip.IsIPVersion(constant.IPv6)).To(Succeed())
+		})
+	})
+
+	Describe("Test ParseIP", func() {
+		Describe("IP format", func() {
+			When("Verifying", func() {
+				It("inputs invalid IP version", func() {
+					ip, err := spiderpoolip.ParseIP(invalidIPVersion, "172.18.40.40", false)
+					Expect(err).To(MatchError(spiderpoolip.ErrInvalidIPVersion))
+					Expect(ip).To(BeNil())
+				})
+
+				It("inputs invalid IP address", func() {
+					ip, err := spiderpoolip.ParseIP(constant.IPv4, invalidIP, false)
+					Expect(err).To(MatchError(spiderpoolip.ErrInvalidIPFormat))
+					Expect(ip).To(BeNil())
+				})
+			})
+
+			It("parses IPv4 IP address", func() {
+				ip, err := spiderpoolip.ParseIP(constant.IPv4, "172.18.40.40", false)
+				Expect(err).NotTo(HaveOccurred())
+				Expect(ip).To(Equal(
+					&net.IPNet{
+						IP:   net.IPv4(172, 18, 40, 40),
+						Mask: net.CIDRMask(32, 32),
+					},
+				))
+			})
+
+			It("parses IPv6 IP address", func() {
+				ip, err := spiderpoolip.ParseIP(constant.IPv6, "abcd:1234::1", false)
+				Expect(err).NotTo(HaveOccurred())
+				Expect(ip).To(Equal(
+					&net.IPNet{
+						IP:   net.ParseIP("abcd:1234::1"),
+						Mask: net.CIDRMask(128, 128),
+					},
+				))
+			})
+		})
+
+		Describe("CIDR format", func() {
+			When("Verifying", func() {
+				It("inputs invalid IP version", func() {
+					ip, err := spiderpoolip.ParseIP(invalidIPVersion, "172.18.40.40/24", true)
+					Expect(err).To(MatchError(spiderpoolip.ErrInvalidIPVersion))
+					Expect(ip).To(BeNil())
+				})
+
+				It("inputs invalid CIDR address", func() {
+					ip, err := spiderpoolip.ParseIP(constant.IPv4, invalidCIDR, true)
+					Expect(err).To(MatchError(spiderpoolip.ErrInvalidCIDRFormat))
+					Expect(ip).To(BeNil())
+				})
+			})
+
+			It("parses IPv4 CIDR address", func() {
+				ip, err := spiderpoolip.ParseIP(constant.IPv4, "172.18.40.40/24", true)
+				Expect(err).NotTo(HaveOccurred())
+				Expect(ip).To(Equal(
+					&net.IPNet{
+						IP:   net.IPv4(172, 18, 40, 40),
+						Mask: net.CIDRMask(24, 32),
+					},
+				))
+			})
+
+			It("parses IPv6 CIDR address", func() {
+				ip, err := spiderpoolip.ParseIP(constant.IPv6, "abcd:1234::1/120", true)
+				Expect(err).NotTo(HaveOccurred())
+				Expect(ip).To(Equal(
+					&net.IPNet{
+						IP:   net.ParseIP("abcd:1234::1"),
+						Mask: net.CIDRMask(120, 128),
+					},
+				))
+			})
+		})
+	})
+
+	Describe("Test ContainsIP", func() {
+		When("Verifying", func() {
+			It("inputs invalid IP version", func() {
+				contains, err := spiderpoolip.ContainsIP(invalidIPVersion, "172.18.40.0/24", "172.18.40.40")
+				Expect(err).To(MatchError(spiderpoolip.ErrInvalidIPVersion))
+				Expect(contains).To(BeFalse())
+			})
+
+			It("inputs invalid subnet", func() {
+				contains, err := spiderpoolip.ContainsIP(constant.IPv4, invalidCIDR, "172.18.40.40")
+				Expect(err).To(MatchError(spiderpoolip.ErrInvalidCIDRFormat))
+				Expect(contains).To(BeFalse())
+			})
+
+			It("inputs invalid IP address", func() {
+				contains, err := spiderpoolip.ContainsIP(constant.IPv4, "172.18.40.0/24", invalidIP)
+				Expect(err).To(MatchError(spiderpoolip.ErrInvalidIPFormat))
+				Expect(contains).To(BeFalse())
+			})
+		})
+
+		When("IPv4", func() {
+			It("tests that a subnet contains the IP address", func() {
+				contains, err := spiderpoolip.ContainsIP(constant.IPv4, "172.18.40.0/24", "172.18.40.40")
+				Expect(err).NotTo(HaveOccurred())
+				Expect(contains).To(BeTrue())
+			})
+
+			It("test that a subnet does not contain the IP address", func() {
+				contains, err := spiderpoolip.ContainsIP(constant.IPv4, "172.18.41.0/24", "172.18.40.40")
+				Expect(err).NotTo(HaveOccurred())
+				Expect(contains).To(BeFalse())
+			})
+		})
+
+		When("IPv6", func() {
+			It("tests that a subnet contains the IP address", func() {
+				contains, err := spiderpoolip.ContainsIP(constant.IPv6, "abcd:1234::/120", "abcd:1234::1")
+				Expect(err).NotTo(HaveOccurred())
+				Expect(contains).To(BeTrue())
+			})
+
+			It("test that a subnet does not contain the IP address", func() {
+				contains, err := spiderpoolip.ContainsIP(constant.IPv6, "abcd:1235::/120", "abcd:1234::1")
+				Expect(err).NotTo(HaveOccurred())
+				Expect(contains).To(BeFalse())
+			})
+		})
+	})
+
+	Describe("Test IsIP", func() {
+		When("Verifying", func() {
+			It("inputs invalid IP version", func() {
+				err := spiderpoolip.IsIP(invalidIPVersion, "172.18.40.40")
+				Expect(err).To(MatchError(spiderpoolip.ErrInvalidIPVersion))
+			})
+
+			It("inputs invalid IP address", func() {
+				err := spiderpoolip.IsIP(constant.IPv4, invalidIP)
+				Expect(err).To(MatchError(spiderpoolip.ErrInvalidIPFormat))
+			})
+		})
+
+		It("is IPv4 IP address", func() {
+			Expect(spiderpoolip.IsIP(constant.IPv4, "172.18.40.40")).To(Succeed())
+		})
+
+		It("is IPv6 IP address", func() {
+			Expect(spiderpoolip.IsIP(constant.IPv6, "abcd:1234::1")).To(Succeed())
+		})
+	})
+
+	Describe("Test IPsDiffSet", func() {
+		It("finds the difference set of two groups of IPv4 addresses", func() {
+			ips := spiderpoolip.IPsDiffSet(
+				[]net.IP{
+					net.IPv4(172, 18, 40, 1),
+					net.IPv4(172, 18, 40, 2),
+				},
+				[]net.IP{
+					net.IPv4(172, 18, 40, 2),
+					net.IPv4(172, 18, 40, 3),
+				},
+			)
+			Expect(ips).To(Equal([]net.IP{net.IPv4(172, 18, 40, 1)}))
+		})
+
+		It("finds the difference set of two groups of IPv6 addresses", func() {
+			ips := spiderpoolip.IPsDiffSet(
+				[]net.IP{
+					net.ParseIP("abcd:1234::1"),
+					net.ParseIP("abcd:1234::2"),
+				},
+				[]net.IP{
+					net.ParseIP("abcd:1234::2"),
+					net.ParseIP("abcd:1234::3"),
+				},
+			)
+			Expect(ips).To(Equal([]net.IP{net.ParseIP("abcd:1234::1")}))
+		})
+	})
+
+	Describe("Test IPsUnionSet", func() {
+		It("finds the union set of two groups of IPv4 addresses", func() {
+			ips := spiderpoolip.IPsUnionSet(
+				[]net.IP{
+					net.IPv4(172, 18, 40, 1),
+					net.IPv4(172, 18, 40, 2),
+				},
+				[]net.IP{
+					net.IPv4(172, 18, 40, 2),
+					net.IPv4(172, 18, 40, 3),
+				},
+			)
+			Expect(ips).To(Equal(
+				[]net.IP{
+					net.IPv4(172, 18, 40, 1),
+					net.IPv4(172, 18, 40, 2),
+					net.IPv4(172, 18, 40, 3),
+				}),
+			)
+		})
+
+		It("finds the union set of two groups of IPv6 addresses", func() {
+			ips := spiderpoolip.IPsUnionSet(
+				[]net.IP{
+					net.ParseIP("abcd:1234::1"),
+					net.ParseIP("abcd:1234::2"),
+				},
+				[]net.IP{
+					net.ParseIP("abcd:1234::2"),
+					net.ParseIP("abcd:1234::3"),
+				},
+			)
+			Expect(ips).To(Equal(
+				[]net.IP{
+					net.ParseIP("abcd:1234::1"),
+					net.ParseIP("abcd:1234::2"),
+					net.ParseIP("abcd:1234::3"),
+				}),
+			)
+		})
+	})
+
+	Describe("Test IPsIntersectionSet", func() {
+		It("finds the intersection set of two groups of IPv4 addresses", func() {
+			ips := spiderpoolip.IPsIntersectionSet(
+				[]net.IP{
+					net.IPv4(172, 18, 40, 1),
+					net.IPv4(172, 18, 40, 2),
+				},
+				[]net.IP{
+					net.IPv4(172, 18, 40, 2),
+					net.IPv4(172, 18, 40, 3),
+				},
+			)
+			Expect(ips).To(Equal([]net.IP{net.IPv4(172, 18, 40, 2)}))
+		})
+
+		It("finds the intersection set of two groups of IPv6 addresses", func() {
+			ips := spiderpoolip.IPsIntersectionSet(
+				[]net.IP{
+					net.ParseIP("abcd:1234::1"),
+					net.ParseIP("abcd:1234::2"),
+				},
+				[]net.IP{
+					net.ParseIP("abcd:1234::2"),
+					net.ParseIP("abcd:1234::3"),
+				},
+			)
+			Expect(ips).To(Equal([]net.IP{net.ParseIP("abcd:1234::2")}))
+		})
+	})
+
+	Describe("Test NextIP", func() {
+		It("returns the next IP address of the IPv4 IP address", func() {
+			ip := spiderpoolip.NextIP(net.IPv4(172, 18, 40, 40))
+			Expect(ip).To(Equal(net.IPv4(172, 18, 40, 41)))
+		})
+
+		It("returns the next IP address of the IPv6 IP address", func() {
+			ip := spiderpoolip.NextIP(net.ParseIP("abcd:1234::1"))
+			Expect(ip).To(Equal(net.ParseIP("abcd:1234::2")))
+		})
+	})
+
+	Describe("Test PrevIP", func() {
+		It("returns the previous IP address of the IPv4 IP address", func() {
+			ip := spiderpoolip.PrevIP(net.IPv4(172, 18, 40, 40))
+			Expect(ip).To(Equal(net.IPv4(172, 18, 40, 39)))
+		})
+
+		It("returns the previous IP address of the IPv6 IP address", func() {
+			ip := spiderpoolip.PrevIP(net.ParseIP("abcd:1234::1"))
+			Expect(ip).To(Equal(net.ParseIP("abcd:1234::0")))
+		})
+	})
+
+	Describe("Test Cmp", func() {
+		It("compares two IPv4 IP addresses", func() {
+			Expect(spiderpoolip.Cmp(
+				net.IPv4(172, 18, 40, 1),
+				net.IPv4(172, 18, 40, 2),
+			)).To(BeNumerically("<", 0))
+
+			Expect(spiderpoolip.Cmp(
+				net.IPv4(172, 18, 40, 1),
+				net.IPv4(172, 18, 40, 1),
+			)).To(BeNumerically("==", 0))
+
+			Expect(spiderpoolip.Cmp(
+				net.IPv4(172, 18, 40, 2),
+				net.IPv4(172, 18, 40, 1),
+			)).To(BeNumerically(">", 0))
+		})
+
+		It("compares two IPv6 IP addresses", func() {
+			Expect(spiderpoolip.Cmp(
+				net.ParseIP("abcd:1234::1"),
+				net.ParseIP("abcd:1234::2"),
+			)).To(BeNumerically("<", 0))
+
+			Expect(spiderpoolip.Cmp(
+				net.ParseIP("abcd:1234::1"),
+				net.ParseIP("abcd:1234::1"),
+			)).To(BeNumerically("==", 0))
+
+			Expect(spiderpoolip.Cmp(
+				net.ParseIP("abcd:1234::2"),
+				net.ParseIP("abcd:1234::1"),
+			)).To(BeNumerically(">", 0))
+		})
+	})
+})
diff --git a/pkg/ip/iprange_test.go b/pkg/ip/iprange_test.go
new file mode 100644
index 000000000..887aabab4
--- /dev/null
+++ b/pkg/ip/iprange_test.go
@@ -0,0 +1,384 @@
+// Copyright 2022 Authors of spidernet-io
+// SPDX-License-Identifier: Apache-2.0
+
+package ip_test
+
+import (
+	"net"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+
+	"github.com/spidernet-io/spiderpool/pkg/constant"
+	spiderpoolip "github.com/spidernet-io/spiderpool/pkg/ip"
+)
+
+var _ = Describe("IP range", Label("ip_range_test"), func() {
+	Describe("Test MergeIPRanges", func() {
+		When("Verifying", func() {
+			It("inputs invalid IP version", func() {
+				ranges, err := spiderpoolip.MergeIPRanges(invalidIPVersion, []string{"172.18.40.10"})
+				Expect(err).To(MatchError(spiderpoolip.ErrInvalidIPVersion))
+				Expect(ranges).To(BeEmpty())
+			})
+
+			It("inputs invalid IP ranges", func() {
+				ranges, err := spiderpoolip.MergeIPRanges(constant.IPv4, invalidIPRanges)
+				Expect(err).To(MatchError(spiderpoolip.ErrInvalidIPRangeFormat))
+				Expect(ranges).To(BeEmpty())
+			})
+		})
+
+		It("merges IPv4 IP ranges", func() {
+			ranges, err := spiderpoolip.MergeIPRanges(constant.IPv4,
+				[]string{
+					"172.18.40.10",
+					"172.18.40.1-172.18.40.3",
+					"172.18.40.2-172.18.40.5",
+				},
+			)
+			Expect(err).NotTo(HaveOccurred())
+			Expect(ranges).To(Equal(
+				[]string{
+					"172.18.40.1-172.18.40.5",
+					"172.18.40.10",
+				},
+			))
+		})
+
+		It("merges IPv6 IP ranges", func() {
+			ranges, err := spiderpoolip.MergeIPRanges(constant.IPv6,
+				[]string{
+					"abcd:1234::a",
+					"abcd:1234::1-abcd:1234::3",
+					"abcd:1234::2-abcd:1234::5",
+				},
+			)
+			Expect(err).NotTo(HaveOccurred())
+			Expect(ranges).To(Equal(
+				[]string{
+					"abcd:1234::1-abcd:1234::5",
+					"abcd:1234::a",
+				},
+			))
+		})
+	})
+
+	Describe("Test ParseIPRanges", func() {
+		When("Verifying", func() {
+			It("inputs invalid IP version", func() {
+				ips, err := spiderpoolip.ParseIPRanges(invalidIPVersion, []string{"172.18.40.10"})
+				Expect(err).To(MatchError(spiderpoolip.ErrInvalidIPVersion))
+				Expect(ips).To(BeEmpty())
+			})
+
+			It("inputs invalid IP ranges", func() {
+				ips, err := spiderpoolip.ParseIPRanges(constant.IPv4, invalidIPRanges)
+				Expect(err).To(MatchError(spiderpoolip.ErrInvalidIPRangeFormat))
+				Expect(ips).To(BeEmpty())
+			})
+		})
+
+		It("parses IPv4 IP ranges", func() {
+			ips, err := spiderpoolip.ParseIPRanges(constant.IPv4, []string{"172.18.40.10"})
+			Expect(err).NotTo(HaveOccurred())
+			Expect(ips).To(Equal([]net.IP{net.IPv4(172, 18, 40, 10)}))
+
+			ips, err = spiderpoolip.ParseIPRanges(constant.IPv4,
+				[]string{
+					"172.18.40.10",
+					"172.18.40.1-172.18.40.2",
+				},
+			)
+			Expect(err).NotTo(HaveOccurred())
+			Expect(ips).To(Equal(
+				[]net.IP{
+					net.IPv4(172, 18, 40, 10),
+					net.IPv4(172, 18, 40, 1),
+					net.IPv4(172, 18, 40, 2),
+				},
+			))
+		})
+
+		It("parses IPv6 IP ranges", func() {
+			ips, err := spiderpoolip.ParseIPRanges(constant.IPv6, []string{"abcd:1234::a"})
+			Expect(err).NotTo(HaveOccurred())
+			Expect(ips).To(Equal([]net.IP{net.ParseIP("abcd:1234::a")}))
+
+			ips, err = spiderpoolip.ParseIPRanges(constant.IPv6,
+				[]string{
+					"abcd:1234::a",
+					"abcd:1234::1-abcd:1234::2",
+				},
+			)
+			Expect(err).NotTo(HaveOccurred())
+			Expect(ips).To(Equal(
+				[]net.IP{
+					net.ParseIP("abcd:1234::a"),
+					net.ParseIP("abcd:1234::1"),
+					net.ParseIP("abcd:1234::2"),
+				},
+			))
+		})
+	})
+
+	Describe("Test ParseIPRange", func() {
+		When("Verifying", func() {
+			It("inputs invalid IP version", func() {
+				ips, err := spiderpoolip.ParseIPRange(invalidIPVersion, "172.18.40.10")
+				Expect(err).To(MatchError(spiderpoolip.ErrInvalidIPVersion))
+				Expect(ips).To(BeEmpty())
+			})
+
+			It("inputs invalid IP ranges", func() {
+				ips, err := spiderpoolip.ParseIPRange(constant.IPv4, invalidIPRange)
+				Expect(err).To(MatchError(spiderpoolip.ErrInvalidIPRangeFormat))
+				Expect(ips).To(BeEmpty())
+			})
+		})
+
+		It("parses IPv4 IP range", func() {
+			ips, err := spiderpoolip.ParseIPRange(constant.IPv4, "172.18.40.10")
+			Expect(err).NotTo(HaveOccurred())
+			Expect(ips).To(Equal([]net.IP{net.IPv4(172, 18, 40, 10)}))
+
+			ips, err = spiderpoolip.ParseIPRange(constant.IPv4, "172.18.40.1-172.18.40.2")
+			Expect(err).NotTo(HaveOccurred())
+			Expect(ips).To(Equal(
+				[]net.IP{
+					net.IPv4(172, 18, 40, 1),
+					net.IPv4(172, 18, 40, 2),
+				},
+			))
+		})
+
+		It("parses IPv6 IP range", func() {
+			ips, err := spiderpoolip.ParseIPRange(constant.IPv6, "abcd:1234::a")
+			Expect(err).NotTo(HaveOccurred())
+			Expect(ips).To(Equal([]net.IP{net.ParseIP("abcd:1234::a")}))
+
+			ips, err = spiderpoolip.ParseIPRange(constant.IPv6, "abcd:1234::1-abcd:1234::2")
+			Expect(err).NotTo(HaveOccurred())
+			Expect(ips).To(Equal(
+				[]net.IP{
+					net.ParseIP("abcd:1234::1"),
+					net.ParseIP("abcd:1234::2"),
+				},
+			))
+		})
+	})
+
+	Describe("Test ConvertIPsToIPRanges", func() {
+		When("Verifying", func() {
+			It("inputs invalid IP version", func() {
+				ranges, err := spiderpoolip.ConvertIPsToIPRanges(invalidIPVersion, []net.IP{net.IPv4(172, 18, 40, 10)})
+				Expect(err).To(MatchError(spiderpoolip.ErrInvalidIPVersion))
+				Expect(ranges).To(BeEmpty())
+			})
+
+			It("inputs unmatched IP addresses", func() {
+				ranges, err := spiderpoolip.ConvertIPsToIPRanges(constant.IPv6, []net.IP{net.IPv4(172, 18, 40, 10)})
+				Expect(err).To(MatchError(spiderpoolip.ErrInvalidIP))
+				Expect(ranges).To(BeEmpty())
+			})
+		})
+
+		It("converts IPv4 IP addresses to IP ranges", func() {
+			ranges, err := spiderpoolip.ConvertIPsToIPRanges(constant.IPv4, []net.IP{net.IPv4(172, 18, 40, 10)})
+			Expect(err).NotTo(HaveOccurred())
+			Expect(ranges).To(Equal([]string{"172.18.40.10"}))
+
+			ranges, err = spiderpoolip.ConvertIPsToIPRanges(constant.IPv4,
+				[]net.IP{
+					net.IPv4(172, 18, 40, 10),
+					net.IPv4(172, 18, 40, 1),
+					net.IPv4(172, 18, 40, 2),
+				},
+			)
+			Expect(err).NotTo(HaveOccurred())
+			Expect(ranges).To(Equal(
+				[]string{
+					"172.18.40.1-172.18.40.2",
+					"172.18.40.10",
+				},
+			))
+		})
+
+		It("converts IPv6 IP addresses to IP ranges", func() {
+			ranges, err := spiderpoolip.ConvertIPsToIPRanges(constant.IPv6, []net.IP{net.ParseIP("abcd:1234::a")})
+			Expect(err).NotTo(HaveOccurred())
+			Expect(ranges).To(Equal([]string{"abcd:1234::a"}))
+
+			ranges, err = spiderpoolip.ConvertIPsToIPRanges(constant.IPv6,
+				[]net.IP{
+					net.ParseIP("abcd:1234::a"),
+					net.ParseIP("abcd:1234::1"),
+					net.ParseIP("abcd:1234::2"),
+				},
+			)
+			Expect(err).NotTo(HaveOccurred())
+			Expect(ranges).To(Equal(
+				[]string{
+					"abcd:1234::1-abcd:1234::2",
+					"abcd:1234::a",
+				},
+			))
+		})
+	})
+
+	Describe("Test ContainsIPRange", func() {
+		When("Verifying", func() {
+			It("inputs invalid IP version", func() {
+				contains, err := spiderpoolip.ContainsIPRange(invalidIPVersion, "172.18.40.0/24", "172.18.40.10")
+				Expect(err).To(MatchError(spiderpoolip.ErrInvalidIPVersion))
+				Expect(contains).To(BeFalse())
+			})
+
+			It("inputs invalid subnet", func() {
+				contains, err := spiderpoolip.ContainsIPRange(constant.IPv4, invalidCIDR, "172.18.40.10")
+				Expect(err).To(MatchError(spiderpoolip.ErrInvalidCIDRFormat))
+				Expect(contains).To(BeFalse())
+			})
+
+			It("inputs invalid IP range", func() {
+				contains, err := spiderpoolip.ContainsIPRange(constant.IPv4, "172.18.40.0/24", invalidIPRange)
+				Expect(err).To(MatchError(spiderpoolip.ErrInvalidIPRangeFormat))
+				Expect(contains).To(BeFalse())
+			})
+		})
+
+		When("IPv4", func() {
+			It("tests that a subnet contains the IP range", func() {
+				contains, err := spiderpoolip.ContainsIPRange(constant.IPv4, "172.18.40.0/24", "172.18.40.10")
+				Expect(err).NotTo(HaveOccurred())
+				Expect(contains).To(BeTrue())
+
+				contains, err = spiderpoolip.ContainsIPRange(constant.IPv4, "172.18.40.0/24", "172.18.40.1-172.18.40.2")
+				Expect(err).NotTo(HaveOccurred())
+				Expect(contains).To(BeTrue())
+			})
+
+			It("test that a subnet does not contain the IP range", func() {
+				contains, err := spiderpoolip.ContainsIPRange(constant.IPv4, "172.18.40.0/24", "172.18.40.254-172.18.41.1")
+				Expect(err).NotTo(HaveOccurred())
+				Expect(contains).To(BeFalse())
+			})
+		})
+
+		When("IPv6", func() {
+			It("tests that a subnet contains the IP range", func() {
+				contains, err := spiderpoolip.ContainsIPRange(constant.IPv6, "abcd:1234::/120", "abcd:1234::a")
+				Expect(err).NotTo(HaveOccurred())
+				Expect(contains).To(BeTrue())
+
+				contains, err = spiderpoolip.ContainsIPRange(constant.IPv6, "abcd:1234::/120", "abcd:1234::1-abcd:1234::2")
+				Expect(err).NotTo(HaveOccurred())
+				Expect(contains).To(BeTrue())
+			})
+
+			It("test that a subnet does not contain the IP range", func() {
+				contains, err := spiderpoolip.ContainsIPRange(constant.IPv6, "abcd:1234::/120", "abcd:1234::fd-abcd:1234::101")
+				Expect(err).NotTo(HaveOccurred())
+				Expect(contains).To(BeFalse())
+			})
+		})
+	})
+
+	Describe("Test IsIPRangeOverlap", func() {
+		When("Verifying", func() {
+			It("inputs invalid IP version", func() {
+				overlap, err := spiderpoolip.IsIPRangeOverlap(invalidIPVersion, "172.18.40.1-172.18.40.2", "172.18.40.2-172.18.40.3")
+				Expect(err).To(MatchError(spiderpoolip.ErrInvalidIPVersion))
+				Expect(overlap).To(BeFalse())
+			})
+
+			It("inputs invalid IP range", func() {
+				overlap, err := spiderpoolip.IsIPRangeOverlap(constant.IPv4, invalidIPRange, "172.18.40.2-172.18.40.3")
+				Expect(err).To(MatchError(spiderpoolip.ErrInvalidIPRangeFormat))
+				Expect(overlap).To(BeFalse())
+
+				overlap, err = spiderpoolip.IsIPRangeOverlap(constant.IPv4, "172.18.40.1-172.18.40.2", invalidIPRange)
+				Expect(err).To(MatchError(spiderpoolip.ErrInvalidIPRangeFormat))
+				Expect(overlap).To(BeFalse())
+			})
+		})
+
+		When("IPv4", func() {
+			It("tests that two IP ranges overlap", func() {
+				overlap, err := spiderpoolip.IsIPRangeOverlap(constant.IPv4, "172.18.40.1-172.18.40.2", "172.18.40.2-172.18.40.3")
+				Expect(err).NotTo(HaveOccurred())
+				Expect(overlap).To(BeTrue())
+			})
+
+			It("tests that two IP ranges do not overlap", func() {
+				overlap, err := spiderpoolip.IsIPRangeOverlap(constant.IPv4, "172.18.40.1-172.18.40.2", "172.18.40.3-172.18.40.4")
+				Expect(err).NotTo(HaveOccurred())
+				Expect(overlap).To(BeFalse())
+			})
+		})
+
+		When("IPv6", func() {
+			It("tests that two IP ranges overlap", func() {
+				overlap, err := spiderpoolip.IsIPRangeOverlap(constant.IPv6, "abcd:1234::1-abcd:1234::2", "abcd:1234::2-abcd:1234::3")
+				Expect(err).NotTo(HaveOccurred())
+				Expect(overlap).To(BeTrue())
+			})
+
+			It("tests that two IP ranges do not overlap", func() {
+				overlap, err := spiderpoolip.IsIPRangeOverlap(constant.IPv6, "abcd:1234::1-abcd:1234::2", "abcd:1234::3-abcd:1234::4")
+				Expect(err).NotTo(HaveOccurred())
+				Expect(overlap).To(BeFalse())
+			})
+		})
+	})
+
+	Describe("Test IsIPRange", func() {
+		When("Verifying", func() {
+			It("inputs invalid IP version", func() {
+				err := spiderpoolip.IsIPRange(invalidIPVersion, "172.18.40.1-172.18.40.2")
+				Expect(err).To(MatchError(spiderpoolip.ErrInvalidIPVersion))
+			})
+
+			It("inputs invalid IP range", func() {
+				err := spiderpoolip.IsIPRange(constant.IPv4, invalidIPRange)
+				Expect(err).To(MatchError(spiderpoolip.ErrInvalidIPRangeFormat))
+			})
+		})
+
+		It("is an IPv4 IP range", func() {
+			Expect(spiderpoolip.IsIPRange(constant.IPv4, "172.18.40.10")).To(Succeed())
+			Expect(spiderpoolip.IsIPRange(constant.IPv4, "172.18.40.1-172.18.40.2")).To(Succeed())
+		})
+
+		It("is an IPv6 IP range", func() {
+			Expect(spiderpoolip.IsIPRange(constant.IPv6, "abcd:1234::a")).To(Succeed())
+			Expect(spiderpoolip.IsIPRange(constant.IPv6, "abcd:1234::1-abcd:1234::2")).To(Succeed())
+		})
+	})
+
+	Describe("Test IsIPv4IPRange", func() {
+		It("tests whether it is an IPv4 IP range", func() {
+			Expect(spiderpoolip.IsIPv4IPRange(invalidIPRange)).To(BeFalse())
+			Expect(spiderpoolip.IsIPv4IPRange("172.18.40.1-invalid IP range")).To(BeFalse())
+			Expect(spiderpoolip.IsIPv4IPRange("invalid IP range-172.18.40.2")).To(BeFalse())
+			Expect(spiderpoolip.IsIPv4IPRange("172.18.40.2-172.18.40.1")).To(BeFalse())
+			Expect(spiderpoolip.IsIPv4IPRange("172.18.40.1-172.18.40.2-172.18.40.3")).To(BeFalse())
+
+			Expect(spiderpoolip.IsIPv4IPRange("172.18.40.10")).To(BeTrue())
+			Expect(spiderpoolip.IsIPv4IPRange("172.18.40.1-172.18.40.2")).To(BeTrue())
+		})
+	})
+
+	Describe("Test IsIPv6IPRange", func() {
+		It("tests whether it is an IPv6 IP range", func() {
+			Expect(spiderpoolip.IsIPv6IPRange(invalidIPRange)).To(BeFalse())
+			Expect(spiderpoolip.IsIPv6IPRange("abcd:1234::1-invalid IP range")).To(BeFalse())
+			Expect(spiderpoolip.IsIPv6IPRange("invalid IP range-abcd:1234::2")).To(BeFalse())
+			Expect(spiderpoolip.IsIPv6IPRange("abcd:1234::2-abcd:1234::1")).To(BeFalse())
+			Expect(spiderpoolip.IsIPv6IPRange("abcd:1234::1-abcd:1234::2-abcd:1234::3")).To(BeFalse())
+
+			Expect(spiderpoolip.IsIPv6IPRange("abcd:1234::a")).To(BeTrue())
+			Expect(spiderpoolip.IsIPv6IPRange("abcd:1234::1-abcd:1234::2")).To(BeTrue())
+		})
+	})
+})
diff --git a/pkg/ip/route_test.go b/pkg/ip/route_test.go
new file mode 100644
index 000000000..f8d64001c
--- /dev/null
+++ b/pkg/ip/route_test.go
@@ -0,0 +1,81 @@
+// Copyright 2022 Authors of spidernet-io
+// SPDX-License-Identifier: Apache-2.0
+
+package ip_test
+
+import (
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+
+	"github.com/spidernet-io/spiderpool/pkg/constant"
+	spiderpoolip "github.com/spidernet-io/spiderpool/pkg/ip"
+)
+
+var _ = Describe("Route", Label("route_test"), func() {
+	Describe("Test IsRoute", func() {
+		When("Verifying", func() {
+			It("inputs invalid IP version", func() {
+				err := spiderpoolip.IsRoute(invalidIPVersion, "192.168.40.0/24", "172.18.40.1")
+				Expect(err).To(MatchError(spiderpoolip.ErrInvalidIPVersion))
+			})
+
+			It("inputs invalid routing destination", func() {
+				err := spiderpoolip.IsRoute(constant.IPv4, invalidDst, "172.18.40.1")
+				Expect(err).To(MatchError(spiderpoolip.ErrInvalidRouteFormat))
+			})
+
+			It("inputs invalid routing gateway", func() {
+				err := spiderpoolip.IsRoute(constant.IPv4, "192.168.40.0/24", invalidGateway)
+				Expect(err).To(MatchError(spiderpoolip.ErrInvalidRouteFormat))
+			})
+		})
+
+		It("is an IPv4 route", func() {
+			err := spiderpoolip.IsRoute(constant.IPv4, "192.168.40.0/24", "172.18.40.1")
+			Expect(err).NotTo(HaveOccurred())
+		})
+
+		It("is an IPv6 route", func() {
+			err := spiderpoolip.IsRoute(constant.IPv6, "fd00:40::/120", "abcd:1234::1")
+			Expect(err).NotTo(HaveOccurred())
+		})
+	})
+
+	Describe("Test IsRouteWithoutIPVersion", func() {
+		When("Verifying", func() {
+			It("inputs invalid routing destination", func() {
+				err := spiderpoolip.IsRouteWithoutIPVersion(invalidDst, "172.18.40.1")
+				Expect(err).To(MatchError(spiderpoolip.ErrInvalidRouteFormat))
+			})
+
+			It("inputs invalid routing gateway", func() {
+				err := spiderpoolip.IsRouteWithoutIPVersion("192.168.40.0/24", invalidGateway)
+				Expect(err).To(MatchError(spiderpoolip.ErrInvalidRouteFormat))
+			})
+		})
+
+		It("is an route", func() {
+			err := spiderpoolip.IsRouteWithoutIPVersion("192.168.40.0/24", "172.18.40.1")
+			Expect(err).NotTo(HaveOccurred())
+
+			err = spiderpoolip.IsRouteWithoutIPVersion("fd00:40::/120", "abcd:1234::1")
+			Expect(err).NotTo(HaveOccurred())
+		})
+	})
+
+	Describe("Test IsIPv4Route", func() {
+		It("tests whether it is an IPv4 route", func() {
+			Expect(spiderpoolip.IsIPv4Route(invalidDst, "172.18.40.1")).To(BeFalse())
+			Expect(spiderpoolip.IsIPv4Route("192.168.40.0/24", invalidGateway)).To(BeFalse())
+			Expect(spiderpoolip.IsIPv4Route("192.168.40.0/24", "172.18.40.1")).To(BeTrue())
+		})
+	})
+
+	Describe("Test IsIPv6Route", func() {
+		It("tests whether it is an IPv6 route", func() {
+			Expect(spiderpoolip.IsIPv6Route(invalidDst, "abcd:1234::1")).To(BeFalse())
+			Expect(spiderpoolip.IsIPv6Route("fd00:40::/120", invalidGateway)).To(BeFalse())
+			Expect(spiderpoolip.IsIPv6Route("fd00:40::/120", "abcd:1234::1")).To(BeTrue())
+		})
+	})
+})
diff --git a/pkg/ip/utils_test.go b/pkg/ip/utils_test.go
new file mode 100644
index 000000000..b68e4c414
--- /dev/null
+++ b/pkg/ip/utils_test.go
@@ -0,0 +1,68 @@
+// Copyright 2022 Authors of spidernet-io
+// SPDX-License-Identifier: Apache-2.0
+
+package ip_test
+
+import (
+	"net"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+
+	"github.com/spidernet-io/spiderpool/pkg/constant"
+	spiderpoolip "github.com/spidernet-io/spiderpool/pkg/ip"
+)
+
+var _ = Describe("IP utils", Label("ip_utils_test"), func() {
+	Describe("Test AssembleTotalIPs", func() {
+		When("Verifying", func() {
+			It("inputs invalid IP version", func() {
+				ips, err := spiderpoolip.AssembleTotalIPs(invalidIPVersion, []string{"172.18.40.1-172.18.40.2"}, []string{"172.18.40.2"})
+				Expect(err).To(MatchError(spiderpoolip.ErrInvalidIPVersion))
+				Expect(ips).To(BeEmpty())
+			})
+
+			It("inputs invalid IP ranges", func() {
+				ips, err := spiderpoolip.AssembleTotalIPs(constant.IPv4, invalidIPRanges, []string{"172.18.40.2"})
+				Expect(err).To(MatchError(spiderpoolip.ErrInvalidIPRangeFormat))
+				Expect(ips).To(BeEmpty())
+			})
+
+			It("inputs invalid excluded IP ranges", func() {
+				ips, err := spiderpoolip.AssembleTotalIPs(constant.IPv4, []string{"172.18.40.1-172.18.40.2"}, invalidIPRanges)
+				Expect(err).To(MatchError(spiderpoolip.ErrInvalidIPRangeFormat))
+				Expect(ips).To(BeEmpty())
+			})
+		})
+
+		It("assembles all valid IPv4 IP addresses", func() {
+			ips, err := spiderpoolip.AssembleTotalIPs(constant.IPv4,
+				[]string{
+					"172.18.40.10",
+					"172.18.40.1-172.18.40.2",
+				},
+				[]string{
+					"172.18.40.10",
+					"172.18.40.2-172.18.40.3",
+				},
+			)
+			Expect(err).NotTo(HaveOccurred())
+			Expect(ips).To(Equal([]net.IP{net.IPv4(172, 18, 40, 1)}))
+		})
+
+		It("assembles all valid IPv6 IP addresses", func() {
+			ips, err := spiderpoolip.AssembleTotalIPs(constant.IPv6,
+				[]string{
+					"abcd:1234::a",
+					"abcd:1234::1-abcd:1234::2",
+				},
+				[]string{
+					"abcd:1234::a",
+					"abcd:1234::2-abcd:1234::3",
+				},
+			)
+			Expect(err).NotTo(HaveOccurred())
+			Expect(ips).To(Equal([]net.IP{net.ParseIP("abcd:1234::1")}))
+		})
+	})
+})