Repository: dragonflyoss/Dragonfly2
Pull request #474 (2021-07-18 04:05:57)

Add rbac support
Signed-off-by: yxxhero <aiopsclub@163.com>

<!--- Provide a general summary of your changes in the Title above -->

## Description
add rbac support
<!--- Describe your changes in detail -->

## Related Issue
https://github.com/dragonflyoss/Dragonfly2/issues/376
<!--- This project only accepts pull requests related to open issues -->
<!--- If suggesting a new feature or change, please discuss it in an issue first -->
<!--- If fixing a bug, there should be an issue describing it with steps to reproduce -->
<!--- Please link to the issue here: -->

## Motivation and Context

<!--- Why is this change required? What problem does it solve? -->

## Screenshots (if appropriate):

## Types of changes

<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [ ] Documentation Update (if none of the other choices apply)

## Checklist:

<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->

- [x] Code compiles correctly.
- [x] My code follows the code style of this project.
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [x] I have read the **CONTRIBUTING** document.
- [ ] I have added tests to cover my changes.
- [ ] All new and existing tests passed.


codecov-commenter: # [Codecov](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/474?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) Report
> Merging [#474](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/474?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) (13a624c) into [main](https://codecov.io/gh/dragonflyoss/Dragonfly2/commit/7659def768dc8735fe68abf619487e3986835e05?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) (7659def) will **decrease** coverage by `0.04%`.
> The diff coverage is `n/a`.

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

```diff
@@            Coverage Diff             @@
##             main     #474      +/-   ##
==========================================
- Coverage   36.54%   36.49%   -0.05%     
==========================================
  Files          89       89              
  Lines        6959     6959              
==========================================
- Hits         2543     2540       -3     
- Misses       4048     4051       +3     
  Partials      368      368              
```

| Flag | Coverage Δ | |
|---|---|---|
| e2etests | `?` | |
| unittests | `36.49% <ø> (-0.05%)` | :arrow_down: |

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

| [Impacted Files](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/474?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) | Coverage Δ | |
|---|---|---|
| [client/daemon/peer/peertask\_base.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/474/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-Y2xpZW50L2RhZW1vbi9wZWVyL3BlZXJ0YXNrX2Jhc2UuZ28=) | `32.21% <0.00%> (-0.72%)` | :arrow_down: |


Patch:
diff --git a/client/config/peerhost_linux.go b/client/config/peerhost_linux.go
index cf5605b0..2c799284 100644
--- a/client/config/peerhost_linux.go
+++ b/client/config/peerhost_linux.go
@@ -21,6 +21,7 @@ package config
 import (
 	"net"
 
+	"d7y.io/dragonfly/v2/pkg/basic/dfnet"
 	"golang.org/x/time/rate"
 
 	"d7y.io/dragonfly/v2/client/clientutil"
@@ -42,7 +43,12 @@ var peerHostConfig = DaemonOption{
 	GCInterval:  clientutil.Duration{Duration: DefaultGCInterval},
 	KeepStorage: false,
 	Scheduler: SchedulerOption{
-		NetAddrs:        nil,
+		NetAddrs: []dfnet.NetAddr{
+			{
+				Type: dfnet.TCP,
+				Addr: "127.0.0.1:8002",
+			},
+		},
 		ScheduleTimeout: clientutil.Duration{Duration: DefaultScheduleTimeout},
 	},
 	Host: HostOption{
diff --git a/client/daemon/peer/peertask_base.go b/client/daemon/peer/peertask_base.go
index dd28df4b..2376aae0 100644
--- a/client/daemon/peer/peertask_base.go
+++ b/client/daemon/peer/peertask_base.go
@@ -684,7 +684,7 @@ func (pt *peerTask) getPieceTasks(span trace.Span, curPeerPacket *scheduler.Peer
 		if getErr != nil {
 			span.RecordError(getErr)
 			// fast way to exit retry
-			if curPeerPacket != pt.peerPacket {
+			if curPeerPacket.MainPeer.PeerId != pt.peerPacket.MainPeer.PeerId {
 				pt.Warnf("get piece tasks with error: %s, but peer packet changed, switch to new peer packet, current destPeer %s, new destPeer %s", getErr,
 					curPeerPacket.MainPeer.PeerId, pt.peerPacket.MainPeer.PeerId)
 				peerPacketChanged = true
@@ -709,7 +709,7 @@ func (pt *peerTask) getPieceTasks(span trace.Span, curPeerPacket *scheduler.Peer
 				pt.Errorf("send piece result error: %s, code: %d", peer.PeerId, er)
 			}
 			// fast way to exit retry
-			if curPeerPacket != pt.peerPacket {
+			if curPeerPacket.MainPeer.PeerId != pt.peerPacket.MainPeer.PeerId {
 				pt.Warnf("get empty pieces and peer packet changed, switch to new peer packet, current destPeer %s, new destPeer %s",
 					curPeerPacket.MainPeer.PeerId, pt.peerPacket.MainPeer.PeerId)
 				peerPacketChanged = true
diff --git a/cmd/dependency/dependency.go b/cmd/dependency/dependency.go
index 7a05b027..48d6edf7 100644
--- a/cmd/dependency/dependency.go
+++ b/cmd/dependency/dependency.go
@@ -99,7 +99,7 @@ func InitMonitor(verbose bool, pprofPort int, jaeger string) func() {
 				pprofPort, _ = freeport.GetFreePort()
 			}
 
-			debugAddr := fmt.Sprintf("localhost:%d", pprofPort)
+			debugAddr := fmt.Sprintf("%s:%d", iputils.HostIP, pprofPort)
 			viewer.SetConfiguration(viewer.WithAddr(debugAddr))
 
 			logger.With("pprof", fmt.Sprintf("http://%s/debug/pprof", debugAddr),
diff --git a/deploy/charts/dragonfly/README.md b/deploy/charts/dragonfly/README.md
index 689715f7..a65293a2 100644
--- a/deploy/charts/dragonfly/README.md
+++ b/deploy/charts/dragonfly/README.md
@@ -33,4 +33,4 @@ helm install --namespace dragonfly-system dragonfly https://...
 
 ## Reference
 
-[https://github.com/openkruise/kruise/blob/master/charts/kruise/v0.9.0/README.md](https://github.com/openkruise/kruise/blob/master/charts/kruise/v0.9.0/README.md)
+[https://github.com/dragonflyoss/Dragonfly2/blob/main/deploy/charts/dragonfly/README.md](https://github.com/dragonflyoss/Dragonfly2/blob/main/deploy/charts/dragonfly/README.md)
diff --git a/go.mod b/go.mod
index 68cf5cdd..af7a1f45 100644
--- a/go.mod
+++ b/go.mod
@@ -9,11 +9,13 @@ require (
 	github.com/aliyun/aliyun-oss-go-sdk v2.1.6+incompatible
 	github.com/appleboy/gin-jwt/v2 v2.6.4
 	github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
+	github.com/casbin/casbin/v2 v2.34.1
+	github.com/casbin/gorm-adapter/v3 v3.3.2
 	github.com/colinmarc/hdfs/v2 v2.2.0
 	github.com/docker/go-units v0.4.0
 	github.com/emirpasic/gods v1.12.0
 	github.com/envoyproxy/protoc-gen-validate v0.6.1
-	github.com/gin-gonic/gin v1.7.0
+	github.com/gin-gonic/gin v1.7.2
 	github.com/go-echarts/statsview v0.3.4
 	github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a
 	github.com/go-redis/cache/v8 v8.4.1
@@ -70,7 +72,7 @@ require (
 	gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
 	gorm.io/datatypes v1.0.1
 	gorm.io/driver/mysql v1.0.5
-	gorm.io/gorm v1.21.6
+	gorm.io/gorm v1.21.9
 	honnef.co/go/tools v0.0.1-2020.1.3 // indirect
 	k8s.io/apimachinery v0.20.6 // indirect
 	k8s.io/client-go v11.0.0+incompatible
diff --git a/go.sum b/go.sum
index 16f357cb..62c26a99 100644
--- a/go.sum
+++ b/go.sum
@@ -18,6 +18,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/HuKeping/rbtree v0.0.0-20210106022122-8ad34838eb2b h1:zDhQxG7rm8RLgLgi6NpfaVFsop+zxw5hwhbzHr624us=
 github.com/HuKeping/rbtree v0.0.0-20210106022122-8ad34838eb2b/go.mod h1:bODsl3NElqKlgf1UkBLj67fYmY5DsqkKrrYm/kMT/6Y=
+github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw=
+github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
 github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
 github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
@@ -50,6 +52,11 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
 github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
 github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
 github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
+github.com/casbin/casbin/v2 v2.28.3/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
+github.com/casbin/casbin/v2 v2.34.1 h1:LpxduJ9/A+0hbTD84PWbR0fSSTcZBjM+mcPU0rZcv8E=
+github.com/casbin/casbin/v2 v2.34.1/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
+github.com/casbin/gorm-adapter/v3 v3.3.2 h1:DPbDD63KOlyvtmXfxQiS+3sbkA46i6thOvXqsBzf8KY=
+github.com/casbin/gorm-adapter/v3 v3.3.2/go.mod h1:z0J/CpAznL6MyXMPnzltbLBI6lykprGc1rusy+vMJps=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
@@ -74,6 +81,7 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
 github.com/denisenkom/go-mssqldb v0.9.0 h1:RSohk2RsiZqLZ0zCjtfn3S4Gp4exhpBWHyQ7D0yGjAk=
 github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
@@ -107,8 +115,8 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME
 github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
 github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
 github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
-github.com/gin-gonic/gin v1.7.0 h1:jGB9xAJQ12AIGNB4HguylppmDK1Am9ppF7XnGXXJuoU=
-github.com/gin-gonic/gin v1.7.0/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
+github.com/gin-gonic/gin v1.7.2 h1:Tg03T9yM2xa8j6I3Z3oqLaQRSmKvxPd6g/2HJ6zICFA=
+github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
 github.com/go-echarts/go-echarts/v2 v2.2.3 h1:H8oPdUpzuiV2K8S4xYZa1JRNjP3U0h7HVqvhPrmCk1A=
 github.com/go-echarts/go-echarts/v2 v2.2.3/go.mod h1:6TOomEztzGDVDkOSCFBq3ed7xOYfbOqhaBzD0YV771A=
 github.com/go-echarts/statsview v0.3.4 h1:CCuytRAutdnF901NrR4BzSjHXjUp8OyA3/iopgG/1/Y=
@@ -171,6 +179,7 @@ github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4er
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
+github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
 github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
 github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
 github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -364,8 +373,9 @@ github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgx
 github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
-github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
 github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg=
+github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/lyft/protoc-gen-star v0.5.1/go.mod h1:9toiA3cC7z5uVbODF7kEQ91Xn7XNFkVUl+SrEe+ZORU=
 github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
 github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
@@ -887,20 +897,28 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclp
 gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gorm.io/datatypes v1.0.1 h1:6npnXbBtjpSb7FFVA2dG/llyTN8tvZfbUqs+WyLrYgQ=
 gorm.io/datatypes v1.0.1/go.mod h1:HEHoUU3/PO5ZXfAJcVWl11+zWlE16+O0X2DgJEb4Ixs=
+gorm.io/driver/mysql v1.0.3/go.mod h1:twGxftLBlFgNVNakL7F+P/x9oYqoymG3YYT8cAfI9oI=
 gorm.io/driver/mysql v1.0.5 h1:WAAmvLK2rG0tCOqrf5XcLi2QUwugd4rcVJ/W3aoon9o=
 gorm.io/driver/mysql v1.0.5/go.mod h1:N1OIhHAIhx5SunkMGqWbGFVeh4yTNWKmMo1GOAsohLI=
 gorm.io/driver/postgres v1.0.8 h1:PAgM+PaHOSAeroTjHkCHCBIHHoBIf9RgPWGo8dF2DA8=
 gorm.io/driver/postgres v1.0.8/go.mod h1:4eOzrI1MUfm6ObJU/UcmbXyiHSs8jSwH95G5P5dxcAg=
 gorm.io/driver/sqlite v1.1.4 h1:PDzwYE+sI6De2+mxAneV9Xs11+ZyKV6oxD3wDGkaNvM=
 gorm.io/driver/sqlite v1.1.4/go.mod h1:mJCeTFr7+crvS+TRnWc5Z3UvwxUN1BGBLMrf5LA9DYw=
+gorm.io/driver/sqlserver v1.0.4/go.mod h1:ciEo5btfITTBCj9BkoUVDvgQbUdLWQNqdFY5OGuGnRg=
 gorm.io/driver/sqlserver v1.0.7 h1:uwUtb0kdFwW5PkRbd2KJ2h4wlsqvLSjox1XVg/RnzRE=
 gorm.io/driver/sqlserver v1.0.7/go.mod h1:ng66aHI47ZIKz/vvnxzDoonzmTS8HXP+JYlgg67wOog=
+gorm.io/gorm v1.20.0/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
+gorm.io/gorm v1.20.4/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
 gorm.io/gorm v1.20.7/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
+gorm.io/gorm v1.20.11/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
 gorm.io/gorm v1.20.12/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
 gorm.io/gorm v1.21.3/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
 gorm.io/gorm v1.21.4/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
-gorm.io/gorm v1.21.6 h1:xEFbH7WShsnAM+HeRNv7lOeyqmDAK+dDnf1AMf/cVPQ=
 gorm.io/gorm v1.21.6/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
+gorm.io/gorm v1.21.9 h1:INieZtn4P2Pw6xPJ8MzT0G4WUOsHq3RhfuDF1M6GW0E=
+gorm.io/gorm v1.21.9/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
+gorm.io/plugin/dbresolver v1.1.0 h1:cegr4DeprR6SkLIQlKhJLYxH8muFbJ4SmnojXvoeb00=
+gorm.io/plugin/dbresolver v1.1.0/go.mod h1:tpImigFAEejCALOttyhWqsy4vfa2Uh/vAUVnL5IRF7Y=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/manager/database/database.go b/manager/database/database.go
index 486b1a99..245dd116 100644
--- a/manager/database/database.go
+++ b/manager/database/database.go
@@ -6,6 +6,7 @@ import (
 	"d7y.io/dragonfly/v2/manager/config"
 	"d7y.io/dragonfly/v2/manager/model"
 	"github.com/go-redis/redis/v8"
+	"golang.org/x/crypto/bcrypt"
 	"gorm.io/driver/mysql"
 	"gorm.io/gorm"
 	"gorm.io/gorm/schema"
@@ -74,7 +75,9 @@ func migrate(db *gorm.DB) error {
 
 func seed(db *gorm.DB) error {
 	var cdnClusterCount int64
-	db.Model(model.CDNCluster{}).Count(&cdnClusterCount)
+	if err := db.Model(model.CDNCluster{}).Count(&cdnClusterCount).Error; err != nil {
+		return err
+	}
 	if cdnClusterCount <= 0 {
 		if err := db.Create(&model.CDNCluster{
 			Name:   "cdn-cluster-1",
@@ -84,8 +87,30 @@ func seed(db *gorm.DB) error {
 		}
 	}
 
+	var adminUserCount int64
+	var adminUserName = "admin"
+	if err := db.Model(model.User{}).Where("name = ?", adminUserName).Count(&adminUserCount).Error; err != nil {
+		return err
+	}
+	if adminUserCount <= 0 {
+		encryptedPasswordBytes, err := bcrypt.GenerateFromPassword([]byte("Dragonfly2"), bcrypt.MinCost)
+		if err != nil {
+			return err
+		}
+		if err := db.Create(&model.User{
+			EncryptedPassword: string(encryptedPasswordBytes),
+			Name:              adminUserName,
+			Email:             fmt.Sprintf("%s@Dragonfly2.com", adminUserName),
+			State:             model.UserStateEnabled,
+		}).Error; err != nil {
+			return err
+		}
+	}
+
 	var schedulerClusterCount int64
-	db.Model(model.SchedulerCluster{}).Count(&schedulerClusterCount)
+	if err := db.Model(model.SchedulerCluster{}).Count(&schedulerClusterCount).Error; err != nil {
+		return err
+	}
 	if schedulerClusterCount <= 0 {
 		if err := db.Create(&model.SchedulerCluster{
 			Name:         "scheduler-cluster-1",
diff --git a/manager/handlers/permission.go b/manager/handlers/permission.go
new file mode 100644
index 00000000..aa16b8ec
--- /dev/null
+++ b/manager/handlers/permission.go
@@ -0,0 +1,125 @@
+package handlers
+
+import (
+	"net/http"
+
+	"d7y.io/dragonfly/v2/manager/types"
+	"github.com/gin-gonic/gin"
+)
+
+// @Summary Get PermissionGroups
+// @Description Get PermissionGroups
+// @Tags permission
+// @Produce json
+// @Success 200 {object} RoutesInfo
+// @Failure 400 {object} HTTPError
+// @Failure 500 {object} HTTPError
+// @Router /permission/groups [get]
+
+func (h *Handlers) GetPermissionGroups(g *gin.Engine) func(ctx *gin.Context) {
+	return func(ctx *gin.Context) {
+
+		permissionGroups := h.Service.GetPermissionGroups(g)
+
+		ctx.JSON(http.StatusOK, permissionGroups)
+	}
+}
+
+// @Summary Get User Roles
+// @Description Get User Roles
+// @Tags permission
+// @Produce json
+// @Success 200 {object} RoutesInfo
+// @Failure 400 {object} HTTPError
+// @Failure 500 {object} HTTPError
+// @Router /permission/roles/{subject} [get]
+
+func (h *Handlers) GetRolesForUser(ctx *gin.Context) {
+	var params types.UserRolesParams
+	if err := ctx.ShouldBindUri(&params); err != nil {
+		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
+		return
+	}
+	roles, err := h.Service.GetRolesForUser(params.Subject)
+	if err != nil {
+		ctx.Error(err)
+		return
+	}
+	ctx.JSON(http.StatusOK, gin.H{"roles": roles})
+
+}
+
+// @Summary Judge User Role
+// @Description Judge User Role
+// @Tags permission
+// @Produce json
+// @Success 200 {object}
+// @Failure 400 {object} HTTPError
+// @Failure 500 {object} HTTPError
+// @Router /permission/{subject}/{object}/{action} [get]
+
+func (h *Handlers) HasRoleForUser(ctx *gin.Context) {
+	var params types.UserHasRoleParams
+	if err := ctx.ShouldBindUri(&params); err != nil {
+		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
+		return
+	}
+	if params.Subject == "admin" {
+		ctx.JSON(http.StatusOK, gin.H{"has": true})
+		return
+	}
+	has, err := h.Service.HasRoleForUser(params.Subject, params.Object, params.Action)
+	if err != nil {
+		ctx.Error(err)
+		return
+	}
+	ctx.JSON(http.StatusOK, gin.H{"has": has})
+}
+
+// @Summary Create Permission
+// @Description Create Permission by json config
+// @Tags permission
+// @Accept json
+// @Produce json
+// @Success 200
+// @Failure 400 {object} HTTPError
+// @Failure 500 {object} HTTPError
+// @Router /permission [post]
+
+func (h *Handlers) CreatePermission(ctx *gin.Context) {
+	var json types.PolicyRequest
+	if err := ctx.ShouldBindJSON(&json); err != nil {
+		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
+		return
+	}
+	err := h.Service.CreatePermission(json)
+	if err != nil {
+		ctx.Error(err)
+		return
+	}
+	ctx.Status(http.StatusOK)
+}
+
+// @Summary Destroy Permission
+// @Description Destroy Permission by json config
+// @Tags permission
+// @Accept json
+// @Produce json
+// @Success 200
+// @Failure 400 {object} HTTPError
+// @Failure 500 {object} HTTPError
+// @Router /permission [delete]
+
+func (h *Handlers) DestroyPermission(ctx *gin.Context) {
+	var json types.PolicyRequest
+	if err := ctx.ShouldBindJSON(&json); err != nil {
+		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
+		return
+	}
+	err := h.Service.DestroyPermission(json)
+	if err != nil {
+		ctx.Error(err)
+		return
+	}
+	ctx.Status(http.StatusOK)
+}
diff --git a/manager/middlewares/jwt.go b/manager/middlewares/jwt.go
index e0df6bfa..d9625f16 100644
--- a/manager/middlewares/jwt.go
+++ b/manager/middlewares/jwt.go
@@ -1,8 +1,10 @@
 package middlewares
 
 import (
+	"net/http"
 	"time"
 
+	"d7y.io/dragonfly/v2/manager/model"
 	"d7y.io/dragonfly/v2/manager/service"
 	"d7y.io/dragonfly/v2/manager/types"
 	jwt "github.com/appleboy/gin-jwt/v2"
@@ -10,11 +12,11 @@ import (
 )
 
 type user struct {
-	ID uint
+	userName string
 }
 
 func Jwt(service service.REST) (*jwt.GinJWTMiddleware, error) {
-	var identityKey = "id"
+	var identityKey = "username"
 	authMiddleware, err := jwt.New(&jwt.GinJWTMiddleware{
 		Realm:       "Dragonfly",
 		Key:         []byte("Secret Key"),
@@ -24,9 +26,19 @@ func Jwt(service service.REST) (*jwt.GinJWTMiddleware, error) {
 
 		IdentityHandler: func(c *gin.Context) interface{} {
 			claims := jwt.ExtractClaims(c)
-			return &user{
-				ID: claims[identityKey].(uint),
+			userNmae, ok := claims[identityKey]
+			if !ok {
+				c.JSON(http.StatusUnauthorized, gin.H{
+					"message": "Unavailable token: require username info",
+				})
+				c.Abort()
+				return nil
 			}
+			u := &user{
+				userName: userNmae.(string),
+			}
+			c.Set("userName", u.userName)
+			return u
 		},
 
 		Authenticator: func(c *gin.Context) (interface{}, error) {
@@ -40,15 +52,13 @@ func Jwt(service service.REST) (*jwt.GinJWTMiddleware, error) {
 				return "", jwt.ErrFailedAuthentication
 			}
 
-			return &user{
-				ID: u.ID,
-			}, nil
+			return u, nil
 		},
 
 		PayloadFunc: func(data interface{}) jwt.MapClaims {
-			if u, ok := data.(user); ok {
+			if u, ok := data.(*model.User); ok {
 				return jwt.MapClaims{
-					identityKey: u.ID,
+					identityKey: u.Name,
 				}
 			}
 			return jwt.MapClaims{}
diff --git a/manager/middlewares/rbac.go b/manager/middlewares/rbac.go
new file mode 100644
index 00000000..3a627216
--- /dev/null
+++ b/manager/middlewares/rbac.go
@@ -0,0 +1,48 @@
+package middlewares
+
+import (
+	"net/http"
+
+	"d7y.io/dragonfly/v2/manager/permission/rbac"
+	"github.com/casbin/casbin/v2"
+	"github.com/gin-gonic/gin"
+)
+
+func RBAC(e *casbin.Enforcer) gin.HandlerFunc {
+	return func(c *gin.Context) {
+		userName := c.GetString("userName")
+		// request path
+		p := c.Request.URL.Path
+		permissionGroupName, err := rbac.GetAPIGroupName(p)
+		if err != nil {
+			c.Next()
+			return
+		}
+		// request method
+		m := c.Request.Method
+		action := rbac.HTTPMethodToAction(m)
+		// rbac validation
+		adminRes, err := e.HasRoleForUser(userName, "admin")
+		if err != nil {
+			c.JSON(http.StatusUnauthorized, gin.H{
+				"message": "permission validate error",
+			})
+			c.Abort()
+			return
+		}
+		if adminRes {
+			c.Next()
+			return
+		}
+		res, err := e.Enforce(userName, permissionGroupName, action)
+		if err != nil || !res {
+			c.JSON(http.StatusUnauthorized, gin.H{
+				"message": "permission validate error",
+			})
+			c.Abort()
+			return
+		}
+		c.Next()
+
+	}
+}
diff --git a/manager/permission/rbac/rbac.go b/manager/permission/rbac/rbac.go
new file mode 100644
index 00000000..c8d366c0
--- /dev/null
+++ b/manager/permission/rbac/rbac.go
@@ -0,0 +1,141 @@
+package rbac
+
+import (
+	"errors"
+	"net/http"
+	"regexp"
+	"strings"
+
+	logger "d7y.io/dragonfly/v2/internal/dflog"
+	"d7y.io/dragonfly/v2/pkg/util/stringutils"
+	"github.com/casbin/casbin/v2"
+	"github.com/casbin/casbin/v2/model"
+	gormadapter "github.com/casbin/gorm-adapter/v3"
+	"github.com/gin-gonic/gin"
+	"gorm.io/gorm"
+)
+
+// Syntax for models see https://casbin.org/docs/en/syntax-for-models
+const modelText = `
+[request_definition]
+r = sub, obj, act
+
+[policy_definition]
+p = sub, obj, act
+
+[role_definition]
+g = _, _
+
+[policy_effect]
+e = some(where (p.eft == allow))
+
+[matchers]
+m = g(r.sub, p.sub) && r.obj == p.obj && (r.act == p.act || p.act == "*") || r.sub == "admin"
+`
+
+func NewEnforcer(gdb *gorm.DB) (*casbin.Enforcer, error) {
+	gormAdapter, err := gormadapter.NewAdapterByDB(gdb)
+	if err != nil {
+		return nil, err
+	}
+	m, err := model.NewModelFromString(modelText)
+	if err != nil {
+		return nil, err
+	}
+	enforcer, err := casbin.NewEnforcer(m, gormAdapter)
+	if err != nil {
+		return nil, err
+	}
+	return enforcer, nil
+}
+
+func InitRole(e *casbin.Enforcer, g *gin.Engine) error {
+	systemRoles := SystemRoles(g)
+
+	for _, role := range systemRoles {
+		roleInfo := strings.Split(role, ":")
+		_, err := e.AddPolicy(role, roleInfo[0], roleInfo[1])
+		if err != nil {
+			return err
+		}
+	}
+	logger.Info("init and check role success")
+	return nil
+
+}
+
+func GetAPIGroupName(path string) (string, error) {
+	apiGroupRegexp := regexp.MustCompile(`^/api/v[0-9]+/(?P<apiGroup>[\-_a-zA-Z]+)`)
+	matchs := apiGroupRegexp.FindStringSubmatch(path)
+	if matchs == nil {
+		return "", errors.New("faild to find api group")
+	}
+	apiGroupName := ""
+	regexGroupNames := apiGroupRegexp.SubexpNames()
+	for i, name := range regexGroupNames {
+		if i != 0 && name == "apiGroup" {
+			apiGroupName = matchs[i]
+		}
+	}
+
+	if apiGroupName != "" {
+		return apiGroupName, nil
+	}
+	return "", errors.New("faild to find api group")
+
+}
+
+func RoleName(object, action string) string {
+	if object == "admin" {
+		return "admin"
+	}
+	roleName := ""
+	switch action {
+	case "read":
+		roleName = object + ":" + "read"
+	case "write":
+		roleName = object + ":" + "*"
+	}
+	return roleName
+}
+
+func GetAPIGroupNames(g *gin.Engine) []string {
+	APIGroups := []string{}
+	for _, route := range g.Routes() {
+		apiGroupName, err := GetAPIGroupName(route.Path)
+		if err != nil {
+			continue
+		}
+		if !stringutils.Contains(APIGroups, apiGroupName) {
+			APIGroups = append(APIGroups, apiGroupName)
+		}
+
+	}
+	return APIGroups
+
+}
+
+func SystemRoles(g *gin.Engine) []string {
+	Roles := []string{}
+	policyKeys := []string{"read", "*"}
+
+	for _, apiGroup := range GetAPIGroupNames(g) {
+		for _, p := range policyKeys {
+			if !stringutils.Contains(Roles, apiGroup+":"+p) {
+				Roles = append(Roles, apiGroup+":"+p)
+			}
+
+		}
+	}
+	return Roles
+}
+
+func HTTPMethodToAction(method string) string {
+	action := "read"
+
+	if method == http.MethodDelete || method == http.MethodPatch || method == http.MethodPut || method == http.MethodPost {
+		action = "*"
+	}
+
+	return action
+}
diff --git a/manager/server/router.go b/manager/server/router.go
index 3be46c41..45cf2571 100644
--- a/manager/server/router.go
+++ b/manager/server/router.go
@@ -3,12 +3,14 @@ package server
 import (
 	"d7y.io/dragonfly/v2/manager/handlers"
 	"d7y.io/dragonfly/v2/manager/middlewares"
+	rbacbase "d7y.io/dragonfly/v2/manager/permission/rbac"
 	"d7y.io/dragonfly/v2/manager/service"
+	"github.com/casbin/casbin/v2"
 	"github.com/gin-gonic/gin"
 	ginprometheus "github.com/mcuadros/go-gin-prometheus"
 )
 
-func initRouter(verbose bool, service service.REST) (*gin.Engine, error) {
+func initRouter(verbose bool, service service.REST, enforcer *casbin.Enforcer) (*gin.Engine, error) {
 	// Set mode
 	if !verbose {
 		gin.SetMode(gin.ReleaseMode)
@@ -26,6 +28,7 @@ func initRouter(verbose bool, service service.REST) (*gin.Engine, error) {
 	r.Use(gin.Recovery())
 	r.Use(middlewares.Error())
 
+	rbac := middlewares.RBAC(enforcer)
 	jwt, err := middlewares.Jwt(service)
 	if err != nil {
 		return nil, err
@@ -39,7 +42,7 @@ func initRouter(verbose bool, service service.REST) (*gin.Engine, error) {
 	ai.POST("/signin", jwt.LoginHandler)
 	ai.POST("/signout", jwt.LogoutHandler)
 	ai.POST("/refresh_token", jwt.RefreshHandler)
-	ai.POST("/signup", h.SignUp)
+	ai.POST("/signup", jwt.MiddlewareFunc(), rbac, h.SignUp)
 
 	// Scheduler Cluster
 	sc := apiv1.Group("/scheduler-clusters")
@@ -76,6 +79,14 @@ func initRouter(verbose bool, service service.REST) (*gin.Engine, error) {
 	ci.GET(":id", h.GetCDN)
 	ci.GET("", h.GetCDNs)
 
+	// Permission
+	pn := apiv1.Group("/permission", jwt.MiddlewareFunc(), rbac)
+	pn.POST("", h.CreatePermission)
+	pn.DELETE("", h.DestroyPermission)
+	pn.GET("/groups", h.GetPermissionGroups(r))
+	pn.GET("/roles/:subject", h.GetRolesForUser)
+	pn.GET("/:subject/:object/:action", h.HasRoleForUser)
+
 	// Security Group
 	sg := apiv1.Group("/security-groups")
 	sg.POST("", h.CreateSecurityGroup)
@@ -88,5 +99,12 @@ func initRouter(verbose bool, service service.REST) (*gin.Engine, error) {
 
 	// Health Check
 	r.GET("/healthy/*action", h.GetHealth)
+
+	// Auto init roles and check roles
+	err = rbacbase.InitRole(enforcer, r)
+	if err != nil {
+		return nil, err
+	}
+
 	return r, nil
 }
diff --git a/manager/server/server.go b/manager/server/server.go
index 0aaea551..6eb1d5d7 100644
--- a/manager/server/server.go
+++ b/manager/server/server.go
@@ -24,6 +24,7 @@ import (
 	"d7y.io/dragonfly/v2/manager/cache"
 	"d7y.io/dragonfly/v2/manager/config"
 	"d7y.io/dragonfly/v2/manager/database"
+	"d7y.io/dragonfly/v2/manager/permission/rbac"
 	"d7y.io/dragonfly/v2/manager/searcher"
 	"d7y.io/dragonfly/v2/manager/service"
 	"d7y.io/dragonfly/v2/pkg/rpc"
@@ -49,6 +50,10 @@ func New(cfg *config.Config) (*Server, error) {
 	if err != nil {
 		return nil, err
 	}
+	enforcer, err := rbac.NewEnforcer(db.DB)
+	if err != nil {
+		return nil, err
+	}
 
 	// Initialize cache
 	cache := cache.New(cfg)
@@ -57,13 +62,13 @@ func New(cfg *config.Config) (*Server, error) {
 	searcher := searcher.New()
 
 	// Initialize REST service
-	restService := service.NewREST(db, cache)
+	restService := service.NewREST(db, cache, enforcer)
 
 	// Initialize GRPC service
 	grpcService := service.NewGRPC(db, cache, searcher)
 
 	// Initialize router
-	router, err := initRouter(cfg.Verbose, restService)
+	router, err := initRouter(cfg.Verbose, restService, enforcer)
 	if err != nil {
 		return nil, err
 	}
diff --git a/manager/service/permission.go b/manager/service/permission.go
new file mode 100644
index 00000000..2dc061aa
--- /dev/null
+++ b/manager/service/permission.go
@@ -0,0 +1,86 @@
+package service
+
+import (
+	"fmt"
+	"strings"
+
+	logger "d7y.io/dragonfly/v2/internal/dflog"
+	"d7y.io/dragonfly/v2/manager/permission/rbac"
+	"d7y.io/dragonfly/v2/manager/types"
+	"d7y.io/dragonfly/v2/pkg/util/stringutils"
+	"github.com/gin-gonic/gin"
+)
+
+func (s *rest) GetPermissionGroups(g *gin.Engine) types.PermissionGroups {
+	groups := rbac.GetAPIGroupNames(g)
+	if !stringutils.Contains(groups, "admin") {
+		groups = append(groups, "admin")
+	}
+	return groups
+}
+
+func (s *rest) CreatePermission(json types.PolicyRequest) error {
+	roleName := rbac.RoleName(json.Object, json.Action)
+	res, err := s.enforcer.AddRoleForUser(json.Subject, roleName)
+	if err != nil {
+		return err
+	}
+	if !res {
+		logger.Infof("The role %s of %s already exist. skip!", roleName, json.Subject)
+	}
+	return nil
+}
+
+func (s *rest) GetRolesForUser(subject string) ([]map[string]string, error) {
+	result := []map[string]string{}
+	policyToAction := map[string]string{
+		"read": "read",
+		"*":    "write",
+	}
+	res, err := s.enforcer.GetRolesForUser(subject)
+	if err != nil {
+		return nil, err
+	}
+
+	for _, role := range res {
+		if role == "admin" {
+			result = append(result, map[string]string{"object": "admin", "description": "admin", "action": ""})
+		} else {
+			roleInfo := strings.Split(role, ":")
+			action := policyToAction[roleInfo[1]]
+			result = append(result, map[string]string{"object": roleInfo[0], "description": fmt.Sprintf("%s for %s", action, roleInfo[0]), "action": action})
+		}
+	}
+
+	return result, nil
+}
+
+func (s *rest) HasRoleForUser(subject, object, action string) (bool, error) {
+	roleName := rbac.RoleName(object, action)
+	res, err := s.enforcer.HasRoleForUser(subject, roleName)
+	if err != nil {
+		return false, err
+	}
+	if action == "read" {
+		writeRoleName := rbac.RoleName(object, "write")
+		writeRes, err := s.enforcer.HasRoleForUser(subject, writeRoleName)
+		if err != nil {
+			return false, err
+		}
+		return res || writeRes, nil
+
+	}
+	return res, nil
+}
+
+func (s *rest) DestroyPermission(json types.PolicyRequest) error {
+	roleName := rbac.RoleName(json.Object, json.Action)
+	res, err := s.enforcer.DeleteRoleForUser(json.Subject, roleName)
+	if err != nil {
+		return err
+	}
+	if !res {
+		logger.Infof("The role %s of %s already remove. skip!", roleName, json.Subject)
+	}
+	return nil
+}
diff --git a/manager/service/service.go b/manager/service/service.go
index d7225d1d..a69fa755 100644
--- a/manager/service/service.go
+++ b/manager/service/service.go
@@ -5,6 +5,8 @@ import (
 	"d7y.io/dragonfly/v2/manager/database"
 	"d7y.io/dragonfly/v2/manager/model"
 	"d7y.io/dragonfly/v2/manager/types"
+	"github.com/casbin/casbin/v2"
+	"github.com/gin-gonic/gin"
 	"github.com/go-redis/redis/v8"
 	"gorm.io/gorm"
 )
@@ -56,19 +58,27 @@ type REST interface {
 
 	SignIn(json types.SignInRequest) (*model.User, error)
 	SignUp(json types.SignUpRequest) (*model.User, error)
+
+	GetPermissionGroups(g *gin.Engine) types.PermissionGroups
+	CreatePermission(json types.PolicyRequest) error
+	DestroyPermission(json types.PolicyRequest) error
+	GetRolesForUser(subject string) ([]map[string]string, error)
+	HasRoleForUser(subject, object, action string) (bool, error)
 }
 
 type rest struct {
-	db    *gorm.DB
-	rdb   *redis.Client
-	cache *cache.Cache
+	db       *gorm.DB
+	rdb      *redis.Client
+	cache    *cache.Cache
+	enforcer *casbin.Enforcer
 }
 
 // NewREST returns a new REST instence
-func NewREST(database *database.Database, cache *cache.Cache) REST {
+func NewREST(database *database.Database, cache *cache.Cache, enforcer *casbin.Enforcer) REST {
 	return &rest{
-		db:    database.DB,
-		rdb:   database.RDB,
-		cache: cache,
+		db:       database.DB,
+		rdb:      database.RDB,
+		cache:    cache,
+		enforcer: enforcer,
 	}
 }
diff --git a/manager/types/permission.go b/manager/types/permission.go
new file mode 100644
index 00000000..ff35a4e2
--- /dev/null
+++ b/manager/types/permission.go
@@ -0,0 +1,19 @@
+package types
+
+type PolicyRequest struct {
+	Subject string `form:"subject" binding:"required,min=1"`
+	Object  string `form:"object" binding:"required,min=1"`
+	Action  string `form:"aciton" binding:"omitempty,oneof=read write"`
+}
+
+type PermissionGroups []string
+
+type UserRolesParams struct {
+	Subject string `uri:"subject" binding:"required"`
+}
+
+type UserHasRoleParams struct {
+	UserRolesParams
+	Object string `uri:"object" binding:"required"`
+	Action string `uri:"action" binding:"omitempty,oneof=read write"`
+}
diff --git a/pkg/rpc/client.go b/pkg/rpc/client.go
index 505acd58..3456e6be 100644
--- a/pkg/rpc/client.go
+++ b/pkg/rpc/client.go
@@ -214,7 +214,10 @@ func (conn *Connection) findCandidateClientConn(key string, exclusiveNodes ...st
 		}
 		if selected {
 			if client, ok := conn.node2ClientMap.Load(node); ok {
-				return client.(*candidateClient), nil
+				return &candidateClient{
+					node: candidateNode,
+					Ref:  client,
+				}, nil
 			}
 		}
 	}
@@ -226,7 +229,7 @@ func (conn *Connection) findCandidateClientConn(key string, exclusiveNodes ...st
 	if len(ringNodes) == 0 {
 		return nil, dferrors.ErrNoCandidateNode
 	}
-	candidateNodes := make([]string, 0, 0)
+	candidateNodes := make([]string, 0)
 	for _, ringNode := range ringNodes {
 		candidate := true
 		for _, exclusiveNode := range exclusiveNodes {
diff --git a/pkg/rpc/scheduler/client/peer_packet_stream.go b/pkg/rpc/scheduler/client/peer_packet_stream.go
index a62bbf01..d3983dba 100644
--- a/pkg/rpc/scheduler/client/peer_packet_stream.go
+++ b/pkg/rpc/scheduler/client/peer_packet_stream.go
@@ -19,7 +19,6 @@ package client
 import (
 	"context"
 	"io"
-	"time"
 
 	"github.com/pkg/errors"
 	"google.golang.org/grpc"
@@ -56,7 +55,7 @@ func newPeerPacketStream(ctx context.Context, sc *schedulerClient, hashKey strin
 
 	pps := &peerPacketStream{
 		sc:      sc,
-		ctx:     ctx,
+		ctx:     context.Background(),
 		hashKey: hashKey,
 		ptr:     ptr,
 		opts:    opts,
@@ -126,9 +125,9 @@ func (pps *peerPacketStream) retryRecv(cause error) (*scheduler.PeerPacket, erro
 		if err != nil {
 			return nil, err
 		}
-		timeCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
-		defer cancel()
-		_, err = client.RegisterPeerTask(timeCtx, pps.ptr)
+		//timeCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+		//defer cancel()
+		_, err = client.RegisterPeerTask(pps.ctx, pps.ptr)
 		if err != nil {
 			return nil, err
 		}
@@ -198,9 +197,9 @@ func (pps *peerPacketStream) replaceClient(cause error) error {
 		if err != nil {
 			return nil, err
 		}
-		timeCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
-		defer cancel()
-		_, err = client.RegisterPeerTask(timeCtx, pps.ptr)
+		//timeCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+		//defer cancel()
+		_, err = client.RegisterPeerTask(pps.ctx, pps.ptr)
 		if err != nil {
 			return nil, err
 		}
diff --git a/scheduler/core/events.go b/scheduler/core/events.go
index aee72229..6e9b24f4 100644
--- a/scheduler/core/events.go
+++ b/scheduler/core/events.go
@@ -74,11 +74,7 @@ func (s *state) start() {
 			s.waitScheduleParentPeerQueue.AddAfter(peer, time.Second)
 			continue
 		}
-		if peer.PacketChan == nil {
-			logger.Errorf("waitScheduleParentPeerQueue: there is no packet chan associated with peer %s", peer.PeerID)
-			return
-		}
-		peer.PacketChan <- constructSuccessPeerPacket(peer, parent, candidates)
+		peer.SendSchedulePacket(constructSuccessPeerPacket(peer, parent, candidates))
 		logger.WithTaskAndPeerID(peer.Task.TaskID,
 			peer.PeerID).Debugf("waitScheduleParentPeerQueue: peer has left from waitScheduleParentPeerQueue because it has scheduled new parent %v", parent)
 		s.waitScheduleParentPeerQueue.Done(v)
@@ -104,15 +100,11 @@ func (e startReportPieceResultEvent) apply(s *state) {
 		s.waitScheduleParentPeerQueue.AddAfter(e.peer, time.Second)
 		return
 	}
-	if e.peer.PacketChan == nil {
-		logger.Errorf("start report piece result: there is no packet chan associated with peer %s", e.peer.PeerID)
-		return
-	}
-	e.peer.PacketChan <- constructSuccessPeerPacket(e.peer, parent, candidates)
+	e.peer.SendSchedulePacket(constructSuccessPeerPacket(e.peer, parent, candidates))
 }
 
 func (e startReportPieceResultEvent) hashKey() string {
-	return e.peer.PeerID
+	return e.peer.Task.TaskID
 }
 
 type peerDownloadPieceSuccessEvent struct {
@@ -140,17 +132,13 @@ func (e peerDownloadPieceSuccessEvent) apply(s *state) {
 	if oldParent != nil {
 		candidates = append(candidates, oldParent)
 	}
-	if e.peer.PacketChan == nil {
-		logger.Errorf("peerDownloadPieceSuccessEvent: there is no packet chan with peer %s", e.peer.PeerID)
-		return
-	}
 	// TODO if parentPeer is equal with oldParent, need schedule again ?
-	e.peer.PacketChan <- constructSuccessPeerPacket(e.peer, parentPeer, candidates)
+	e.peer.SendSchedulePacket(constructSuccessPeerPacket(e.peer, parentPeer, candidates))
 	return
 }
 
 func (e peerDownloadPieceSuccessEvent) hashKey() string {
-	return e.peer.PeerID
+	return e.peer.Task.TaskID
 }
 
 type peerDownloadPieceFailEvent struct {
@@ -183,7 +171,7 @@ func (e peerDownloadPieceFailEvent) apply(s *state) {
 	}
 }
 func (e peerDownloadPieceFailEvent) hashKey() string {
-	return e.peer.PeerID
+	return e.peer.Task.TaskID
 }
 
 type peerReplaceParentEvent struct {
@@ -191,7 +179,7 @@ type peerReplaceParentEvent struct {
 }
 
 func (e peerReplaceParentEvent) hashKey() string {
-	return e.peer.PeerID
+	return e.peer.Task.TaskID
 }
 
 func (e peerReplaceParentEvent) apply(s *state) {
@@ -226,20 +214,13 @@ func (e peerDownloadSuccessEvent) apply(s *state) {
 	removePeerFromCurrentTree(e.peer, s)
 	children := s.sched.ScheduleChildren(e.peer)
 	for _, child := range children {
-		if child.PacketChan == nil {
-			logger.Debugf("reportPeerSuccessResult: there is no packet chan with peer %s", e.peer.PeerID)
-			continue
-		}
-		child.PacketChan <- constructSuccessPeerPacket(child, e.peer, nil)
-	}
-	if e.peer.PacketChan != nil {
-		close(e.peer.PacketChan)
-		e.peer.PacketChan = nil
+		child.SendSchedulePacket(constructSuccessPeerPacket(child, e.peer, nil))
 	}
+	e.peer.UnBindSendChannel()
 }
 
 func (e peerDownloadSuccessEvent) hashKey() string {
-	return e.peer.PeerID
+	return e.peer.Task.TaskID
 }
 
 type peerDownloadFailEvent struct {
@@ -260,22 +241,14 @@ func (e peerDownloadFailEvent) apply(s *state) {
 			s.waitScheduleParentPeerQueue.AddAfter(e.peer, time.Second)
 			return true
 		}
-		if child.PacketChan == nil {
-			logger.Warnf("reportPeerFailResult: there is no packet chan associated with peer %s", e.peer.PeerID)
-			return true
-		}
-		child.PacketChan <- constructSuccessPeerPacket(child, parent, candidates)
+		child.SendSchedulePacket(constructSuccessPeerPacket(child, parent, candidates))
 		return true
 	})
-	if e.peer.PacketChan != nil {
-		close(e.peer.PacketChan)
-		e.peer.PacketChan = nil
-	}
 	s.peerManager.Delete(e.peer.PeerID)
 }
 
 func (e peerDownloadFailEvent) hashKey() string {
-	return e.peer.PeerID
+	return e.peer.Task.TaskID
 }
 
 type peerLeaveEvent struct {
@@ -289,7 +262,7 @@ func (e peerLeaveEvent) apply(s *state) {
 }
 
 func (e peerLeaveEvent) hashKey() string {
-	return e.peer.PeerID
+	return e.peer.Task.TaskID
 }
 
 func constructSuccessPeerPacket(peer *types.Peer, parent *types.Peer, candidates []*types.Peer) *schedulerRPC.PeerPacket {
@@ -337,11 +310,7 @@ func handlePeerLeave(peer *types.Peer, s *state) {
 			s.waitScheduleParentPeerQueue.AddAfter(child, time.Second)
 			return true
 		}
-		if child.PacketChan == nil {
-			logger.Debugf("handlePeerLeave: there is no packet chan with peer %s", child.PeerID)
-			return true
-		}
-		child.PacketChan <- constructSuccessPeerPacket(child, parent, candidates)
+		child.SendSchedulePacket(constructSuccessPeerPacket(child, parent, candidates))
 		return true
 	})
 	s.peerManager.Delete(peer.PeerID)
@@ -355,22 +324,14 @@ func handleReplaceParent(peer *types.Peer, s *state) {
 		s.waitScheduleParentPeerQueue.AddAfter(peer, time.Second)
 		return
 	}
-	if peer.PacketChan == nil {
-		logger.Errorf("handleReplaceParent: there is no packet chan with peer %s", peer.PeerID)
-		return
-	}
-	peer.PacketChan <- constructSuccessPeerPacket(peer, parent, candidates)
+	peer.SendSchedulePacket(constructSuccessPeerPacket(peer, parent, candidates))
 }
 
 func handleSeedTaskFail(task *types.Task) {
 	if task.IsFail() {
 		task.ListPeers().Range(func(data sortedlist.Item) bool {
 			peer := data.(*types.Peer)
-			if peer.PacketChan == nil {
-				logger.Debugf("taskSeedFailEvent: there is no packet chan with peer %s", peer.PeerID)
-				return true
-			}
-			peer.PacketChan <- constructFailPeerPacket(peer, dfcodes.CdnError)
+			peer.SendSchedulePacket(constructFailPeerPacket(peer, dfcodes.CdnError))
 			return true
 		})
 	}
@@ -383,11 +344,7 @@ func removePeerFromCurrentTree(peer *types.Peer, s *state) {
 	if parent != nil {
 		children := s.sched.ScheduleChildren(parent)
 		for _, child := range children {
-			if child.PacketChan == nil {
-				logger.Debugf("removePeerFromCurrentTree: there is no packet chan with peer %s", peer.PeerID)
-				continue
-			}
-			child.PacketChan <- constructSuccessPeerPacket(child, peer, nil)
+			child.SendSchedulePacket(constructSuccessPeerPacket(child, peer, nil))
 		}
 	}
 }
diff --git a/scheduler/core/monitor.go b/scheduler/core/monitor.go
index f6b5e6ac..dd23de57 100644
--- a/scheduler/core/monitor.go
+++ b/scheduler/core/monitor.go
@@ -87,7 +87,7 @@ func (m *monitor) printDebugInfo() string {
 			parentNode = peer.GetParent().PeerID
 		}
 
-		table.Append([]string{peer.PeerID, peer.Task.URL[len(peer.Task.URL)-15 : len(peer.Task.URL)-5], parentNode, peer.GetStatus().String(),
+		table.Append([]string{peer.PeerID, peer.Task.URL[len(peer.Task.URL)-15 : len(peer.Task.URL)], parentNode, peer.GetStatus().String(),
 			peer.CreateTime.String(),
 			strconv.Itoa(int(peer.GetFinishedNum())),
 			strconv.FormatBool(peer.IsSuccess()), strconv.Itoa(peer.Host.GetFreeUploadLoad())})
diff --git a/scheduler/core/scheduler_service.go b/scheduler/core/scheduler_service.go
index defba62e..e10f70f2 100644
--- a/scheduler/core/scheduler_service.go
+++ b/scheduler/core/scheduler_service.go
@@ -184,6 +184,9 @@ func (s *SchedulerService) GetOrCreateTask(ctx context.Context, task *types.Task
 	if task.IsFrozen() {
 		task.SetStatus(types.TaskStatusRunning)
 	}
+	//if s.config.DisableCDN {
+	// TODO NeedBackSource
+	//}
 	go func() {
 		if err := s.cdnManager.StartSeedTask(ctx, task); err != nil {
 			if !task.IsSuccess() {
diff --git a/scheduler/core/worker.go b/scheduler/core/worker.go
index 7f35c168..2c14a162 100644
--- a/scheduler/core/worker.go
+++ b/scheduler/core/worker.go
@@ -64,7 +64,7 @@ func (wg *workerGroup) send(e event) bool {
 
 func (wg *workerGroup) stop() {
 	close(wg.stopCh)
-	wg.s.start()
+	wg.s.stop()
 	for _, worker := range wg.workerList {
 		worker.stop()
 	}
diff --git a/scheduler/daemon/cdn/d7y/manager.go b/scheduler/daemon/cdn/d7y/manager.go
index 083d0900..5bedc2f8 100644
--- a/scheduler/daemon/cdn/d7y/manager.go
+++ b/scheduler/daemon/cdn/d7y/manager.go
@@ -114,7 +114,7 @@ func (cm *manager) StartSeedTask(ctx context.Context, task *types.Task) error {
 }
 
 func (cm *manager) receivePiece(task *types.Task, stream *client.PieceSeedStream) error {
-	var once sync.Once
+	var initialized bool
 	var cdnPeer *types.Peer
 	for {
 		piece, err := stream.Recv()
@@ -138,9 +138,10 @@ func (cm *manager) receivePiece(task *types.Task, stream *client.PieceSeedStream
 			return errors.Wrapf(ErrCDNInvokeFail, "receive piece from cdn: %v", err)
 		}
 		if piece != nil {
-			once.Do(func() {
+			if !initialized {
 				cdnPeer, err = cm.initCdnPeer(task, piece)
-			})
+				initialized = true
+			}
 			if err != nil || cdnPeer == nil {
 				return err
 			}
diff --git a/scheduler/daemon/peer/manager.go b/scheduler/daemon/peer/manager.go
index b6e77252..d450f486 100644
--- a/scheduler/daemon/peer/manager.go
+++ b/scheduler/daemon/peer/manager.go
@@ -70,11 +70,7 @@ func (m *manager) Delete(peerID string) {
 	if ok {
 		peer.Host.DeletePeer(peerID)
 		peer.Task.DeletePeer(peer)
-		if peer.PacketChan != nil {
-			close(peer.PacketChan)
-			logger.Infof("close peer %s stream", peerID)
-			peer.PacketChan = nil
-		}
+		peer.UnBindSendChannel()
 		m.peerMap.Delete(peerID)
 	}
 	return
diff --git a/scheduler/server/service/scheduler_server.go b/scheduler/server/service/scheduler_server.go
index d190dca0..9aa0c0eb 100644
--- a/scheduler/server/service/scheduler_server.go
+++ b/scheduler/server/service/scheduler_server.go
@@ -33,6 +33,8 @@ import (
 	"d7y.io/dragonfly/v2/scheduler/core"
 	"d7y.io/dragonfly/v2/scheduler/types"
 	"golang.org/x/sync/errgroup"
+	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/status"
 )
 
 type SchedulerServer struct {
@@ -118,24 +120,33 @@ func (s *SchedulerServer) RegisterPeerTask(ctx context.Context, request *schedul
 
 func (s *SchedulerServer) ReportPieceResult(stream scheduler.Scheduler_ReportPieceResultServer) error {
 	peerPacketChan := make(chan *scheduler.PeerPacket, 1)
+	var peer *types.Peer
+	var initialized bool
+	ctx, cancel := context.WithCancel(stream.Context())
+	g, ctx := errgroup.WithContext(ctx)
 	var once sync.Once
-	g, ctx := errgroup.WithContext(context.Background())
-	stopCh := make(chan struct{})
 	g.Go(func() error {
+		defer func() {
+			cancel()
+			once.Do(peer.UnBindSendChannel)
+		}()
 		for {
-			var peer *types.Peer
 			select {
 			case <-ctx.Done():
 				return nil
-			case <-stopCh:
-				return nil
 			default:
 				pieceResult, err := stream.Recv()
 				if err == io.EOF {
 					return nil
 				}
 				if err != nil {
-					return dferrors.Newf(dfcodes.SchedPeerPieceResultReportFail, "peer piece result report error")
+					if status.Code(err) == codes.Canceled {
+						if peer != nil {
+							logger.Info("peer %s canceled", peer.PeerID)
+							return nil
+						}
+					}
+					return dferrors.Newf(dfcodes.SchedPeerPieceResultReportFail, "peer piece result report error: %v", err)
 				}
 				logger.Debugf("report piece result %v of peer %s", pieceResult, pieceResult.SrcPid)
 				var ok bool
@@ -143,10 +154,11 @@ func (s *SchedulerServer) ReportPieceResult(stream scheduler.Scheduler_ReportPie
 				if !ok {
 					return dferrors.Newf(dfcodes.SchedPeerNotFound, "peer %s not found", pieceResult.SrcPid)
 				}
-				once.Do(func() {
+				if !initialized {
 					peer.BindSendChannel(peerPacketChan)
 					peer.SetStatus(types.PeerStatusRunning)
-				})
+					initialized = true
+				}
 				if err := s.service.HandlePieceResult(peer, pieceResult); err != nil {
 					logger.Errorf("handle piece result %v fail: %v", pieceResult, err)
 				}
@@ -155,21 +167,22 @@ func (s *SchedulerServer) ReportPieceResult(stream scheduler.Scheduler_ReportPie
 	})
 
 	g.Go(func() error {
+		defer func() {
+			cancel()
+			once.Do(peer.UnBindSendChannel)
+		}()
 		for {
 			select {
 			case <-ctx.Done():
 				return nil
-			case <-stopCh:
-				return nil
 			case pp, ok := <-peerPacketChan:
 				if !ok {
-					close(stopCh)
 					return nil
 				}
 				err := stream.Send(pp)
 				if err != nil {
 					logger.Errorf("send peer %s schedule packet %v failed: %v", pp.SrcPid, pp, err)
-					return err
+					return dferrors.Newf(dfcodes.SchedPeerPieceResultReportFail, "peer piece result report error: %v", err)
 				}
 			}
 		}
diff --git a/scheduler/types/peer.go b/scheduler/types/peer.go
index 0675ee42..28ce9712 100644
--- a/scheduler/types/peer.go
+++ b/scheduler/types/peer.go
@@ -59,8 +59,10 @@ type Peer struct {
 	Task *Task
 	// Host specifies
 	Host *PeerHost
+	// bindPacketChan
+	bindPacketChan bool
 	// PacketChan send schedulerPacket to peer client
-	PacketChan chan *scheduler.PeerPacket
+	packetChan chan *scheduler.PeerPacket
 	// createTime
 	CreateTime time.Time
 	// finishedNum specifies downloaded finished piece number
@@ -293,11 +295,27 @@ func (peer *Peer) SetStatus(status PeerStatus) {
 func (peer *Peer) BindSendChannel(packetChan chan *scheduler.PeerPacket) {
 	peer.lock.Lock()
 	defer peer.lock.Unlock()
-	peer.PacketChan = packetChan
+	peer.bindPacketChan = true
+	peer.packetChan = packetChan
 }
 
-func (peer *Peer) GetSendChannel() chan *scheduler.PeerPacket {
-	return peer.PacketChan
+func (peer *Peer) UnBindSendChannel() {
+	peer.lock.Lock()
+	defer peer.lock.Unlock()
+	if peer.bindPacketChan {
+		if peer.packetChan != nil {
+			close(peer.packetChan)
+		}
+		peer.bindPacketChan = false
+	}
+}
+
+func (peer *Peer) SendSchedulePacket(packet *scheduler.PeerPacket) {
+	peer.lock.Lock()
+	defer peer.lock.Unlock()
+	if peer.bindPacketChan {
+		peer.packetChan <- packet
+	}
 }
 
 func (peer *Peer) IsRunning() bool {


Test patch:
diff --git a/manager/permission/rbac/rbac_test.go b/manager/permission/rbac/rbac_test.go
new file mode 100644
index 00000000..0c6a177b
--- /dev/null
+++ b/manager/permission/rbac/rbac_test.go
@@ -0,0 +1,92 @@
+package rbac
+
+import (
+	"testing"
+)
+
+func TestGetApiGroupName(t *testing.T) {
+	tests := []struct {
+		path              string
+		exceptedGroupName string
+		hasError          bool
+	}{
+		{
+			path:              "/api/v1/users",
+			exceptedGroupName: "users",
+			hasError:          false,
+		},
+		{
+			path:              "/api/user",
+			exceptedGroupName: "",
+			hasError:          true,
+		},
+	}
+
+	for _, tt := range tests {
+		groupName, err := GetAPIGroupName(tt.path)
+		if tt.hasError {
+			if err == nil {
+				t.Errorf("GetApiGroupName(%s) should return error", tt.path)
+			}
+		}
+
+		if groupName != tt.exceptedGroupName {
+			t.Errorf("GetApiGroupName(%v) = %v, want %v", tt.path, groupName, tt.exceptedGroupName)
+		}
+	}
+
+}
+
+func TestRoleName(t *testing.T) {
+	tests := []struct {
+		object           string
+		action           string
+		exceptedRoleName string
+	}{
+		{
+			object:           "users",
+			action:           "read",
+			exceptedRoleName: "users:read",
+		},
+		{
+			object:           "cdns",
+			action:           "write",
+			exceptedRoleName: "cdns:*",
+		},
+	}
+
+	for _, tt := range tests {
+		roleName := RoleName(tt.object, tt.action)
+		if roleName != tt.exceptedRoleName {
+			t.Errorf("RoleName(%v, %v) = %v, want %v", tt.object, tt.action, roleName, tt.exceptedRoleName)
+		}
+	}
+
+}
+func TestHTTPMethodToAction(t *testing.T) {
+	tests := []struct {
+		method         string
+		exceptedAction string
+	}{
+		{
+			method:         "GET",
+			exceptedAction: "read",
+		},
+		{
+			method:         "POST",
+			exceptedAction: "*",
+		},
+		{
+			method:         "UNKNOWN",
+			exceptedAction: "read",
+		},
+	}
+
+	for _, tt := range tests {
+		action := HTTPMethodToAction(tt.method)
+		if action != tt.exceptedAction {
+			t.Errorf("HttpMethodToAction(%v) = %v, want %v", tt.method, action, tt.exceptedAction)
+		}
+	}
+
+}