Repository: dragonflyoss/Dragonfly2
Pull request #335 (2021-06-13 10:55:12)

return error in NewManagerServer func
Signed-off-by: yxxhero <aiopsclub@163.com>

<!--- Provide a general summary of your changes in the Title above -->

## Description
return error in NewManagerServer func
<!--- Describe your changes in detail -->

## Related Issue

<!--- 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
Tips for more error details when call NewManagerServer func.
<!--- 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/335?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) Report
> Merging [#335](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/335?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) (8f2cc2d) into [main](https://codecov.io/gh/dragonflyoss/Dragonfly2/commit/6b0b3b6d95135be4345930d2579cc5f19ac6e5c1?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) (6b0b3b6) will **decrease** coverage by `0.21%`.
> The diff coverage is `73.33%`.

[![Impacted file tree graph](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/335/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/335?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss)

```diff
@@            Coverage Diff             @@
##             main     #335      +/-   ##
==========================================
- Coverage   40.84%   40.63%   -0.22%     
==========================================
  Files          60       60              
  Lines        4759     4760       +1     
==========================================
- Hits         1944     1934      -10     
- Misses       2571     2579       +8     
- Partials      244      247       +3     
```

| Flag | Coverage Δ | |
|---|---|---|
| unittests | `40.63% <73.33%> (-0.22%)` | :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/335?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) | Coverage Δ | |
|---|---|---|
| [manager/server/server.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/335/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-bWFuYWdlci9zZXJ2ZXIvc2VydmVyLmdv) | `68.18% <73.33%> (-1.59%)` | :arrow_down: |
| [pkg/util/net/iputils/ip\_utils.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/335/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-cGtnL3V0aWwvbmV0L2lwdXRpbHMvaXBfdXRpbHMuZ28=) | `61.22% <0.00%> (-4.09%)` | :arrow_down: |
| [client/daemon/peer/peertask\_stream.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/335/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-Y2xpZW50L2RhZW1vbi9wZWVyL3BlZXJ0YXNrX3N0cmVhbS5nbw==) | `58.11% <0.00%> (-1.14%)` | :arrow_down: |
| [client/daemon/peer/piece\_manager.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/335/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-Y2xpZW50L2RhZW1vbi9wZWVyL3BpZWNlX21hbmFnZXIuZ28=) | `66.80% <0.00%> (-0.80%)` | :arrow_down: |
| [client/daemon/peer/peertask\_base.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/335/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-Y2xpZW50L2RhZW1vbi9wZWVyL3BlZXJ0YXNrX2Jhc2UuZ28=) | `32.76% <0.00%> (-0.73%)` | :arrow_down: |


Patch:
diff --git a/cdnsystem/daemon/mgr/gc_mgr.go b/cdnsystem/cdnutil/cdn_util_test.go
similarity index 62%
rename from cdnsystem/daemon/mgr/gc_mgr.go
rename to cdnsystem/cdnutil/cdn_util_test.go
index 66110637..e1ff8ae1 100644
--- a/cdnsystem/daemon/mgr/gc_mgr.go
+++ b/cdnsystem/cdnutil/cdn_util_test.go
@@ -14,17 +14,18 @@
  * limitations under the License.
  */
 
-package mgr
+package cdnutil
 
-import "context"
+import (
+	"fmt"
+	"testing"
 
-// GCMgr as an interface defines all operations about gc operation.
-type GCMgr interface {
+	"d7y.io/dragonfly/v2/pkg/util/net/iputils"
+)
 
-	// StartGC starts to execute GC
-	StartGC(ctx context.Context) error
-
-	// GCTask is used to do the gc task job with specified taskID.
-	// The CDN file will be deleted when the full is true.
-	GCTask(ctx context.Context, taskID string, full bool) error
+func TestGenCdnPeerID(t *testing.T) {
+	var taskID = "123456"
+	if got, want := GenCDNPeerID(taskID), fmt.Sprint(iputils.HostName, "-", taskID, "_CDN"); got != want {
+		t.Errorf("GenCdnPeerID() = %v, want %v", got, want)
+	}
 }
diff --git a/cdnsystem/config/config.go b/cdnsystem/config/config.go
index 675690e9..39453e7a 100644
--- a/cdnsystem/config/config.go
+++ b/cdnsystem/config/config.go
@@ -19,9 +19,9 @@ package config
 import (
 	"time"
 
-	"d7y.io/dragonfly/v2/cdnsystem/daemon/mgr/cdn/storage"
-	"d7y.io/dragonfly/v2/cdnsystem/daemon/mgr/cdn/storage/disk"
-	"d7y.io/dragonfly/v2/cdnsystem/daemon/mgr/cdn/storage/hybrid"
+	"d7y.io/dragonfly/v2/cdnsystem/daemon/cdn/storage"
+	"d7y.io/dragonfly/v2/cdnsystem/daemon/cdn/storage/disk"
+	"d7y.io/dragonfly/v2/cdnsystem/daemon/cdn/storage/hybrid"
 	"d7y.io/dragonfly/v2/cdnsystem/plugins"
 	"d7y.io/dragonfly/v2/cdnsystem/storedriver"
 	"d7y.io/dragonfly/v2/cdnsystem/storedriver/local"
@@ -67,7 +67,7 @@ func NewDefaultPlugins() map[plugins.PluginType][]*plugins.PluginProperties {
 				},
 			}, {
 				Name:   local.MemoryDriverName,
-				Enable: true,
+				Enable: false,
 				Config: &storedriver.Config{
 					BaseDir: DefaultMemoryBaseDir,
 				},
diff --git a/cdnsystem/daemon/mgr/cdn/cache_data_mgr.go b/cdnsystem/daemon/cdn/cache_data_mgr.go
similarity index 97%
rename from cdnsystem/daemon/mgr/cdn/cache_data_mgr.go
rename to cdnsystem/daemon/cdn/cache_data_mgr.go
index d8d9533f..2e71ba7d 100644
--- a/cdnsystem/daemon/mgr/cdn/cache_data_mgr.go
+++ b/cdnsystem/daemon/cdn/cache_data_mgr.go
@@ -20,15 +20,15 @@ import (
 	"bytes"
 	"fmt"
 	"io"
-	"time"
 
-	"d7y.io/dragonfly/v2/cdnsystem/daemon/mgr/cdn/storage"
+	"d7y.io/dragonfly/v2/cdnsystem/daemon/cdn/storage"
 	"d7y.io/dragonfly/v2/cdnsystem/storedriver"
 	"d7y.io/dragonfly/v2/cdnsystem/types"
 	logger "d7y.io/dragonfly/v2/pkg/dflog"
 	"d7y.io/dragonfly/v2/pkg/synclock"
 	"d7y.io/dragonfly/v2/pkg/util/digestutils"
 	"d7y.io/dragonfly/v2/pkg/util/stringutils"
+	"d7y.io/dragonfly/v2/pkg/util/timeutils"
 	"github.com/pkg/errors"
 )
 
@@ -79,7 +79,7 @@ func (mm *cacheDataManager) updateAccessTime(taskID string, accessTime int64) er
 	interval := accessTime - originMetaData.AccessTime
 	originMetaData.Interval = interval
 	if interval <= 0 {
-		logger.WithTaskID(taskID).Warnf("file hit interval:%d, accessTime:%v", interval, time.Unix(accessTime/1000, accessTime%1000))
+		logger.WithTaskID(taskID).Warnf("file hit interval:%d, accessTime:%s", interval, timeutils.MillisUnixTime(accessTime))
 		originMetaData.Interval = 0
 	}
 
diff --git a/cdnsystem/daemon/mgr/cdn/cache_detector.go b/cdnsystem/daemon/cdn/cache_detector.go
similarity index 92%
rename from cdnsystem/daemon/mgr/cdn/cache_detector.go
rename to cdnsystem/daemon/cdn/cache_detector.go
index 1fbcecea..a3daf7a8 100644
--- a/cdnsystem/daemon/mgr/cdn/cache_detector.go
+++ b/cdnsystem/daemon/cdn/cache_detector.go
@@ -17,23 +17,24 @@
 package cdn
 
 import (
+	"context"
 	"crypto/md5"
 	"fmt"
 	"hash"
 	"sort"
+	"time"
 
-	"d7y.io/dragonfly/v2/cdnsystem/cdnerrors"
-	"d7y.io/dragonfly/v2/cdnsystem/daemon/mgr/cdn/storage"
-	"d7y.io/dragonfly/v2/cdnsystem/source"
+	"d7y.io/dragonfly/v2/cdnsystem/daemon/cdn/storage"
+	cdnerrors "d7y.io/dragonfly/v2/cdnsystem/errors"
 	"d7y.io/dragonfly/v2/cdnsystem/types"
 	logger "d7y.io/dragonfly/v2/pkg/dflog"
+	"d7y.io/dragonfly/v2/pkg/source"
 	"github.com/pkg/errors"
 )
 
 // cacheDetector detect task cache
 type cacheDetector struct {
 	cacheDataManager *cacheDataManager
-	resourceClient   source.ResourceClient
 }
 
 // cacheResult cache result of detect
@@ -50,10 +51,9 @@ func (s *cacheResult) String() string {
 }
 
 // newCacheDetector create a new cache detector
-func newCacheDetector(cacheDataManager *cacheDataManager, resourceClient source.ResourceClient) *cacheDetector {
+func newCacheDetector(cacheDataManager *cacheDataManager) *cacheDetector {
 	return &cacheDetector{
 		cacheDataManager: cacheDataManager,
-		resourceClient:   resourceClient,
 	}
 }
 
@@ -89,7 +89,9 @@ func (cd *cacheDetector) doDetect(task *types.SeedTask) (result *cacheResult, er
 	if err := checkSameFile(task, fileMetaData); err != nil {
 		return nil, errors.Wrapf(err, "task does not match meta information of task file")
 	}
-	expired, err := cd.resourceClient.IsExpired(task.URL, task.Header, fileMetaData.ExpireInfo)
+	ctx, expireCancel := context.WithTimeout(context.Background(), 4*time.Second)
+	defer expireCancel()
+	expired, err := source.IsExpired(ctx, task.URL, task.Header, fileMetaData.ExpireInfo)
 	if err != nil {
 		// 如果获取失败，则认为没有过期，防止打爆源
 		logger.WithTaskID(task.TaskID).Errorf("failed to check if the task expired: %v", err)
@@ -106,7 +108,9 @@ func (cd *cacheDetector) doDetect(task *types.SeedTask) (result *cacheResult, er
 	}
 	// check if the resource supports range request. if so,
 	// detect the cache situation by reading piece meta and data file
-	supportRange, err := cd.resourceClient.IsSupportRange(task.URL, task.Header)
+	ctx, rangeCancel := context.WithTimeout(context.Background(), 4*time.Second)
+	defer rangeCancel()
+	supportRange, err := source.IsSupportRange(ctx, task.URL, task.Header)
 	if err != nil {
 		return nil, errors.Wrapf(err, "failed to check if url(%s) supports range request", task.URL)
 	}
diff --git a/cdnsystem/daemon/mgr/cdn/cache_detector_util.go b/cdnsystem/daemon/cdn/cache_detector_util.go
similarity index 97%
rename from cdnsystem/daemon/mgr/cdn/cache_detector_util.go
rename to cdnsystem/daemon/cdn/cache_detector_util.go
index 5b983ebf..c972ec91 100644
--- a/cdnsystem/daemon/mgr/cdn/cache_detector_util.go
+++ b/cdnsystem/daemon/cdn/cache_detector_util.go
@@ -22,8 +22,8 @@ import (
 	"hash"
 	"io"
 
-	"d7y.io/dragonfly/v2/cdnsystem/cdnerrors"
-	"d7y.io/dragonfly/v2/cdnsystem/daemon/mgr/cdn/storage"
+	"d7y.io/dragonfly/v2/cdnsystem/daemon/cdn/storage"
+	cdnerrors "d7y.io/dragonfly/v2/cdnsystem/errors"
 	"d7y.io/dragonfly/v2/cdnsystem/types"
 	"d7y.io/dragonfly/v2/pkg/util/digestutils"
 	"d7y.io/dragonfly/v2/pkg/util/ifaceutils"
diff --git a/cdnsystem/daemon/mgr/cdn/cache_writer_util.go b/cdnsystem/daemon/cdn/cache_writer_util.go
similarity index 96%
rename from cdnsystem/daemon/mgr/cdn/cache_writer_util.go
rename to cdnsystem/daemon/cdn/cache_writer_util.go
index 3855d386..6209e46e 100644
--- a/cdnsystem/daemon/mgr/cdn/cache_writer_util.go
+++ b/cdnsystem/daemon/cdn/cache_writer_util.go
@@ -25,7 +25,7 @@ import (
 	"sync"
 
 	"d7y.io/dragonfly/v2/cdnsystem/config"
-	"d7y.io/dragonfly/v2/cdnsystem/daemon/mgr/cdn/storage"
+	"d7y.io/dragonfly/v2/cdnsystem/daemon/cdn/storage"
 	"d7y.io/dragonfly/v2/cdnsystem/types"
 	logger "d7y.io/dragonfly/v2/pkg/dflog"
 	"d7y.io/dragonfly/v2/pkg/util/digestutils"
@@ -62,7 +62,7 @@ func (cw *cacheWriter) writerPool(ctx context.Context, wg *sync.WaitGroup, write
 			defer wg.Done()
 			for job := range jobCh {
 				var pieceMd5 = md5.New()
-				// todo 后续压缩等特性通过waitToWriteContent 和 pieceStyle 实现
+				// todo Subsequent compression and other features are implemented through waitToWriteContent and pieceStyle
 				waitToWriteContent := job.pieceContent
 				// 要写盘数据的长度
 				originPieceLen := waitToWriteContent.Len() // the length of the original data that has not been processed
diff --git a/cdnsystem/daemon/mgr/cdn/cdn_util.go b/cdnsystem/daemon/cdn/cdn_util.go
similarity index 96%
rename from cdnsystem/daemon/mgr/cdn/cdn_util.go
rename to cdnsystem/daemon/cdn/cdn_util.go
index d0afa406..8b2d37d1 100644
--- a/cdnsystem/daemon/mgr/cdn/cdn_util.go
+++ b/cdnsystem/daemon/cdn/cdn_util.go
@@ -17,7 +17,7 @@
 package cdn
 
 import (
-	"d7y.io/dragonfly/v2/cdnsystem/daemon/mgr/cdn/storage"
+	"d7y.io/dragonfly/v2/cdnsystem/daemon/cdn/storage"
 	"d7y.io/dragonfly/v2/cdnsystem/types"
 	"d7y.io/dragonfly/v2/pkg/util/timeutils"
 )
diff --git a/cdnsystem/daemon/mgr/cdn/downloader.go b/cdnsystem/daemon/cdn/downloader.go
similarity index 86%
rename from cdnsystem/daemon/mgr/cdn/downloader.go
rename to cdnsystem/daemon/cdn/downloader.go
index ceff6c93..b28e27f0 100644
--- a/cdnsystem/daemon/mgr/cdn/downloader.go
+++ b/cdnsystem/daemon/cdn/downloader.go
@@ -17,11 +17,13 @@
 package cdn
 
 import (
+	"context"
 	"fmt"
 	"io"
 
 	"d7y.io/dragonfly/v2/cdnsystem/types"
 	logger "d7y.io/dragonfly/v2/pkg/dflog"
+	"d7y.io/dragonfly/v2/pkg/source"
 	"d7y.io/dragonfly/v2/pkg/structure/maputils"
 	"d7y.io/dragonfly/v2/pkg/util/rangeutils"
 	"github.com/pkg/errors"
@@ -29,7 +31,7 @@ import (
 
 const RangeHeaderName = "Range"
 
-func (cm *Manager) download(task *types.SeedTask, detectResult *cacheResult) (io.ReadCloser, map[string]string, error) {
+func (cm *Manager) download(ctx context.Context, task *types.SeedTask, detectResult *cacheResult) (io.ReadCloser, map[string]string, error) {
 	headers := maputils.DeepCopyMap(nil, task.Header)
 	if detectResult.breakPoint > 0 {
 		breakRange, err := rangeutils.GetBreakRange(detectResult.breakPoint, task.SourceFileLength)
@@ -43,5 +45,5 @@ func (cm *Manager) download(task *types.SeedTask, detectResult *cacheResult) (io
 	}
 	logger.WithTaskID(task.TaskID).Infof("start download url %s at range:%d-%d: with header: %+v", task.URL, detectResult.breakPoint,
 		task.SourceFileLength, task.Header)
-	return cm.resourceClient.Download(task.URL, headers)
+	return source.DownloadWithExpire(ctx, task.URL, headers)
 }
diff --git a/cdnsystem/daemon/mgr/cdn/manager.go b/cdnsystem/daemon/cdn/manager.go
similarity index 86%
rename from cdnsystem/daemon/mgr/cdn/manager.go
rename to cdnsystem/daemon/cdn/manager.go
index ba53c8a9..e6ebe6cc 100644
--- a/cdnsystem/daemon/mgr/cdn/manager.go
+++ b/cdnsystem/daemon/cdn/manager.go
@@ -23,15 +23,14 @@ import (
 	"crypto/md5"
 	"fmt"
 
-	_ "d7y.io/dragonfly/v2/cdnsystem/daemon/mgr/cdn/storage/disk"   // To register diskStorage
-	_ "d7y.io/dragonfly/v2/cdnsystem/daemon/mgr/cdn/storage/hybrid" // To register hybridStorage
+	"d7y.io/dragonfly/v2/cdnsystem/daemon"
+	_ "d7y.io/dragonfly/v2/cdnsystem/daemon/cdn/storage/disk"   // To register diskStorage
+	_ "d7y.io/dragonfly/v2/cdnsystem/daemon/cdn/storage/hybrid" // To register hybridStorage
 	"d7y.io/dragonfly/v2/pkg/rpc/cdnsystem/server"
 	"d7y.io/dragonfly/v2/pkg/synclock"
 
 	"d7y.io/dragonfly/v2/cdnsystem/config"
-	"d7y.io/dragonfly/v2/cdnsystem/daemon/mgr"
-	"d7y.io/dragonfly/v2/cdnsystem/daemon/mgr/cdn/storage"
-	"d7y.io/dragonfly/v2/cdnsystem/source"
+	"d7y.io/dragonfly/v2/cdnsystem/daemon/cdn/storage"
 	"d7y.io/dragonfly/v2/cdnsystem/types"
 	logger "d7y.io/dragonfly/v2/pkg/dflog"
 	"d7y.io/dragonfly/v2/pkg/ratelimiter/limitreader"
@@ -40,11 +39,8 @@ import (
 	"github.com/pkg/errors"
 )
 
-func init() {
-	// Ensure that Manager implements the CDNMgr interface
-	var manager *Manager = nil
-	var _ mgr.CDNMgr = manager
-}
+// Ensure that Manager implements the CDNMgr interface
+var _ daemon.CDNMgr = (*Manager)(nil)
 
 // Manager is an implementation of the interface of CDNMgr.
 type Manager struct {
@@ -53,18 +49,21 @@ type Manager struct {
 	limiter          *ratelimiter.RateLimiter
 	cdnLocker        *synclock.LockerPool
 	cacheDataManager *cacheDataManager
-	progressMgr      mgr.SeedProgressMgr
+	progressMgr      daemon.SeedProgressMgr
 	cdnReporter      *reporter
 	detector         *cacheDetector
-	resourceClient   source.ResourceClient
 	writer           *cacheWriter
 }
 
 // NewManager returns a new Manager.
-func NewManager(cfg *config.Config, cacheStore storage.Manager, progressMgr mgr.SeedProgressMgr, resourceClient source.ResourceClient) (mgr.CDNMgr, error) {
+func NewManager(cfg *config.Config, cacheStore storage.Manager, progressMgr daemon.SeedProgressMgr) (daemon.CDNMgr, error) {
+	return newManager(cfg, cacheStore, progressMgr)
+}
+
+func newManager(cfg *config.Config, cacheStore storage.Manager, progressMgr daemon.SeedProgressMgr) (*Manager, error) {
 	rateLimiter := ratelimiter.NewRateLimiter(ratelimiter.TransRate(int64(cfg.MaxBandwidth-cfg.SystemReservedBandwidth)), 2)
 	cacheDataManager := newCacheDataManager(cacheStore)
-	cdnReporter := newReporter(progressMgr, cacheStore)
+	cdnReporter := newReporter(progressMgr)
 	return &Manager{
 		cfg:              cfg,
 		cacheStore:       cacheStore,
@@ -73,8 +72,7 @@ func NewManager(cfg *config.Config, cacheStore storage.Manager, progressMgr mgr.
 		cacheDataManager: cacheDataManager,
 		cdnReporter:      cdnReporter,
 		progressMgr:      progressMgr,
-		detector:         newCacheDetector(cacheDataManager, resourceClient),
-		resourceClient:   resourceClient,
+		detector:         newCacheDetector(cacheDataManager),
 		writer:           newCacheWriter(cdnReporter, cacheDataManager),
 	}, nil
 }
@@ -103,7 +101,7 @@ func (cm *Manager) TriggerCDN(ctx context.Context, task *types.SeedTask) (seedTa
 	server.StatSeedStart(task.TaskID, task.URL)
 	start := time.Now()
 	// third: start to download the source file
-	body, expireInfo, err := cm.download(task, detectResult)
+	body, expireInfo, err := cm.download(ctx, task, detectResult)
 	// download fail
 	if err != nil {
 		server.StatSeedFinish(task.TaskID, task.URL, false, err, start.Nanosecond(), time.Now().Nanosecond(), 0, 0)
@@ -111,8 +109,8 @@ func (cm *Manager) TriggerCDN(ctx context.Context, task *types.SeedTask) (seedTa
 	}
 	defer body.Close()
 
-	//update Expire info
-	cm.updateExpireInfo(ctx, task.TaskID, expireInfo)
+	// update Expire info
+	cm.updateExpireInfo(task.TaskID, expireInfo)
 	fileMd5 := md5.New()
 	if detectResult.fileMd5 != nil {
 		fileMd5 = detectResult.fileMd5
@@ -138,7 +136,7 @@ func (cm *Manager) TriggerCDN(ctx context.Context, task *types.SeedTask) (seedTa
 		downloadMetadata.realSourceFileLength, downloadMetadata.realCdnFileLength), nil
 }
 
-func (cm *Manager) Delete(ctx context.Context, taskID string) error {
+func (cm *Manager) Delete(taskID string) error {
 	err := cm.cacheStore.DeleteTask(taskID)
 	if err != nil {
 		return errors.Wrap(err, "failed to delete task files")
@@ -198,7 +196,7 @@ func (cm *Manager) handleCDNResult(ctx context.Context, task *types.SeedTask, so
 	return true, nil
 }
 
-func (cm *Manager) updateExpireInfo(ctx context.Context, taskID string, expireInfo map[string]string) {
+func (cm *Manager) updateExpireInfo(taskID string, expireInfo map[string]string) {
 	if err := cm.cacheDataManager.updateExpireInfo(taskID, expireInfo); err != nil {
 		logger.WithTaskID(taskID).Errorf("failed to update expireInfo(%s): %v", expireInfo, err)
 	}
diff --git a/cdnsystem/daemon/mgr/cdn/reporter.go b/cdnsystem/daemon/cdn/reporter.go
similarity index 85%
rename from cdnsystem/daemon/mgr/cdn/reporter.go
rename to cdnsystem/daemon/cdn/reporter.go
index 3191d2ed..9a65a0ad 100644
--- a/cdnsystem/daemon/mgr/cdn/reporter.go
+++ b/cdnsystem/daemon/cdn/reporter.go
@@ -19,16 +19,15 @@ package cdn
 import (
 	"context"
 
-	"d7y.io/dragonfly/v2/cdnsystem/daemon/mgr"
-	"d7y.io/dragonfly/v2/cdnsystem/daemon/mgr/cdn/storage"
+	"d7y.io/dragonfly/v2/cdnsystem/daemon"
+	"d7y.io/dragonfly/v2/cdnsystem/daemon/cdn/storage"
 	logger "d7y.io/dragonfly/v2/pkg/dflog"
 	"github.com/pkg/errors"
 	"go.uber.org/zap"
 )
 
 type reporter struct {
-	progress   mgr.SeedProgressMgr
-	cacheStore storage.Manager
+	progress daemon.SeedProgressMgr
 }
 
 const (
@@ -36,10 +35,9 @@ const (
 	DownloaderReport = "download"
 )
 
-func newReporter(publisher mgr.SeedProgressMgr, cacheStore storage.Manager) *reporter {
+func newReporter(publisher daemon.SeedProgressMgr) *reporter {
 	return &reporter{
-		progress:   publisher,
-		cacheStore: cacheStore,
+		progress: publisher,
 	}
 }
 
diff --git a/cdnsystem/daemon/mgr/cdn/storage/disk/disk.go b/cdnsystem/daemon/cdn/storage/disk/disk.go
similarity index 90%
rename from cdnsystem/daemon/mgr/cdn/storage/disk/disk.go
rename to cdnsystem/daemon/cdn/storage/disk/disk.go
index cbe7de41..dfec2650 100644
--- a/cdnsystem/daemon/mgr/cdn/storage/disk/disk.go
+++ b/cdnsystem/daemon/cdn/storage/disk/disk.go
@@ -18,17 +18,16 @@ package disk
 
 import (
 	"bytes"
-	"context"
 	"encoding/json"
 	"fmt"
 	"io"
 	"strings"
 	"time"
 
-	"d7y.io/dragonfly/v2/cdnsystem/cdnerrors"
-	"d7y.io/dragonfly/v2/cdnsystem/daemon/mgr"
-	"d7y.io/dragonfly/v2/cdnsystem/daemon/mgr/cdn/storage"
-	"d7y.io/dragonfly/v2/cdnsystem/daemon/mgr/gc"
+	"d7y.io/dragonfly/v2/cdnsystem/daemon"
+	"d7y.io/dragonfly/v2/cdnsystem/daemon/cdn/storage"
+	"d7y.io/dragonfly/v2/cdnsystem/daemon/gc"
+	cdnerrors "d7y.io/dragonfly/v2/cdnsystem/errors"
 	"d7y.io/dragonfly/v2/cdnsystem/storedriver"
 	"d7y.io/dragonfly/v2/cdnsystem/storedriver/local"
 	"d7y.io/dragonfly/v2/cdnsystem/types"
@@ -40,22 +39,24 @@ import (
 	"github.com/sirupsen/logrus"
 )
 
-const StorageMode = "disk"
+const StorageMode = storage.DiskStorageMode
+
+var (
+	_ gc.Executor     = (*diskStorageMgr)(nil)
+	_ storage.Manager = (*diskStorageMgr)(nil)
+)
 
 func init() {
-	var diskStorage *diskStorageMgr = nil
-	var _ storage.Manager = diskStorage
-	var _ gc.Executor = diskStorage
-	storage.Register(StorageMode, NewStorageManager)
+	storage.Register(StorageMode, newStorageManager)
 }
 
-func NewStorageManager(cfg *storage.Config) (storage.Manager, error) {
+func newStorageManager(cfg *storage.Config) (storage.Manager, error) {
 	if len(cfg.DriverConfigs) != 1 {
 		return nil, fmt.Errorf("disk storage manager should have only one disk driver, cfg's driver number is wrong config: %v", cfg)
 	}
-	diskDriver, err := storedriver.Get(local.DiskDriverName)
-	if err != nil {
-		return nil, fmt.Errorf("find disk driver for disk storage manager failed, config parameter is %#v: %v", cfg, err)
+	diskDriver, ok := storedriver.Get(local.DiskDriverName)
+	if !ok {
+		return nil, fmt.Errorf("can not find disk driver for disk storage manager, config is %#v", cfg)
 	}
 
 	storageMgr := &diskStorageMgr{
@@ -70,7 +71,7 @@ type diskStorageMgr struct {
 	cfg        *storage.Config
 	diskDriver storedriver.Driver
 	cleaner    *storage.Cleaner
-	taskMgr    mgr.SeedTaskMgr
+	taskMgr    daemon.SeedTaskMgr
 }
 
 func (s *diskStorageMgr) getDefaultGcConfig() *storage.GCConfig {
@@ -90,7 +91,7 @@ func (s *diskStorageMgr) getDefaultGcConfig() *storage.GCConfig {
 	}
 }
 
-func (s *diskStorageMgr) Initialize(taskMgr mgr.SeedTaskMgr) {
+func (s *diskStorageMgr) Initialize(taskMgr daemon.SeedTaskMgr) {
 	s.taskMgr = taskMgr
 	diskGcConfig := s.cfg.DriverConfigs[local.DiskDriverName].GCConfig
 	if diskGcConfig == nil {
@@ -121,7 +122,7 @@ func (s *diskStorageMgr) ReadPieceMetaRecords(taskID string) ([]*storage.PieceMe
 	return result, nil
 }
 
-func (s *diskStorageMgr) GC(ctx context.Context) error {
+func (s *diskStorageMgr) GC() error {
 	logger.GcLogger.With("type", "disk").Info("start the disk storage gc job")
 	gcTaskIDs, err := s.cleaner.GC("disk", false)
 	if err != nil {
diff --git a/cdnsystem/daemon/cdn/storage/helper_darwin.go b/cdnsystem/daemon/cdn/storage/helper_darwin.go
new file mode 100644
index 00000000..e2145fae
--- /dev/null
+++ b/cdnsystem/daemon/cdn/storage/helper_darwin.go
@@ -0,0 +1,24 @@
+/*
+ *     Copyright 2020 The Dragonfly Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package storage
+
+func IsSupport(mode string) bool {
+	if mode == DiskStorageMode {
+		return true
+	}
+	return false
+}
diff --git a/cdnsystem/daemon/cdn/storage/helper_linux.go b/cdnsystem/daemon/cdn/storage/helper_linux.go
new file mode 100644
index 00000000..6c95d55e
--- /dev/null
+++ b/cdnsystem/daemon/cdn/storage/helper_linux.go
@@ -0,0 +1,21 @@
+/*
+ *     Copyright 2020 The Dragonfly Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package storage
+
+func IsSupport(mode string) bool {
+	return true
+}
diff --git a/cdnsystem/daemon/mgr/cdn/storage/hybrid/hybrid.go b/cdnsystem/daemon/cdn/storage/hybrid/hybrid.go
similarity index 87%
rename from cdnsystem/daemon/mgr/cdn/storage/hybrid/hybrid.go
rename to cdnsystem/daemon/cdn/storage/hybrid/hybrid.go
index f82ed7b3..e11abf67 100644
--- a/cdnsystem/daemon/mgr/cdn/storage/hybrid/hybrid.go
+++ b/cdnsystem/daemon/cdn/storage/hybrid/hybrid.go
@@ -18,7 +18,6 @@ package hybrid
 
 import (
 	"bytes"
-	"context"
 	"encoding/json"
 	"fmt"
 	"io"
@@ -28,10 +27,10 @@ import (
 	"sync"
 	"time"
 
-	"d7y.io/dragonfly/v2/cdnsystem/cdnerrors"
-	"d7y.io/dragonfly/v2/cdnsystem/daemon/mgr"
-	"d7y.io/dragonfly/v2/cdnsystem/daemon/mgr/cdn/storage"
-	"d7y.io/dragonfly/v2/cdnsystem/daemon/mgr/gc"
+	"d7y.io/dragonfly/v2/cdnsystem/daemon"
+	"d7y.io/dragonfly/v2/cdnsystem/daemon/cdn/storage"
+	"d7y.io/dragonfly/v2/cdnsystem/daemon/gc"
+	cdnerrors "d7y.io/dragonfly/v2/cdnsystem/errors"
 	"d7y.io/dragonfly/v2/cdnsystem/storedriver"
 	"d7y.io/dragonfly/v2/cdnsystem/storedriver/local"
 	"d7y.io/dragonfly/v2/cdnsystem/types"
@@ -43,33 +42,29 @@ import (
 	"go.uber.org/atomic"
 )
 
-const StorageMode = "hybrid"
+const StorageMode = storage.HybridStorageMode
 
 const secureLevel = 500 * unit.MB
 
-func init() {
-
-}
+var _ storage.Manager = (*hybridStorageMgr)(nil)
+var _ gc.Executor = (*hybridStorageMgr)(nil)
 
 func init() {
-	var hybrid *hybridStorageMgr = nil
-	var _ storage.Manager = hybrid
-	var _ gc.Executor = hybrid
-	storage.Register(StorageMode, NewStorageManager)
+	storage.Register(StorageMode, newStorageManager)
 }
 
 // NewStorageManager performs initialization for storage manager and return a storage Manager.
-func NewStorageManager(cfg *storage.Config) (storage.Manager, error) {
+func newStorageManager(cfg *storage.Config) (storage.Manager, error) {
 	if len(cfg.DriverConfigs) != 2 {
 		return nil, fmt.Errorf("disk storage manager should have two driver, cfg's driver number is wrong : %v", cfg)
 	}
-	diskDriver, err := storedriver.Get(local.DiskDriverName)
-	if err != nil {
-		return nil, fmt.Errorf("find disk driver for hybrid storage manager failed, config %v: %v", cfg, err)
+	diskDriver, ok := storedriver.Get(local.DiskDriverName)
+	if !ok {
+		return nil, fmt.Errorf("can not find disk driver for hybrid storage manager, config is %v", cfg)
 	}
-	memoryDriver, err := storedriver.Get(local.MemoryDriverName)
-	if err != nil {
-		return nil, fmt.Errorf("find memory driver for hybrid storage manager failed, config %v: %v", cfg, err)
+	memoryDriver, ok := storedriver.Get(local.MemoryDriverName)
+	if !ok {
+		return nil, fmt.Errorf("can not find memory driver for hybrid storage manager, config %v", cfg)
 	}
 	storageMgr := &hybridStorageMgr{
 		cfg:          cfg,
@@ -82,7 +77,7 @@ func NewStorageManager(cfg *storage.Config) (storage.Manager, error) {
 	return storageMgr, nil
 }
 
-func (h *hybridStorageMgr) Initialize(taskMgr mgr.SeedTaskMgr) {
+func (h *hybridStorageMgr) Initialize(taskMgr daemon.SeedTaskMgr) {
 	h.taskMgr = taskMgr
 	diskGcConfig := h.cfg.DriverConfigs[local.DiskDriverName].GCConfig
 
@@ -90,13 +85,13 @@ func (h *hybridStorageMgr) Initialize(taskMgr mgr.SeedTaskMgr) {
 		diskGcConfig = h.getDiskDefaultGcConfig()
 		logger.GcLogger.With("type", "hybrid").Warnf("disk gc config is nil, use default gcConfig: %v", diskGcConfig)
 	}
-	h.diskStoreCleaner, _ = storage.NewStorageCleaner(diskGcConfig, h.diskDriver, h, taskMgr)
+	h.diskDriverCleaner, _ = storage.NewStorageCleaner(diskGcConfig, h.diskDriver, h, taskMgr)
 	memoryGcConfig := h.cfg.DriverConfigs[local.MemoryDriverName].GCConfig
 	if memoryGcConfig == nil {
 		memoryGcConfig = h.getMemoryDefaultGcConfig()
 		logger.GcLogger.With("type", "hybrid").Warnf("memory gc config is nil, use default gcConfig: %v", diskGcConfig)
 	}
-	h.memoryStoreCleaner, _ = storage.NewStorageCleaner(memoryGcConfig, h.memoryDriver, h, taskMgr)
+	h.memoryDriverCleaner, _ = storage.NewStorageCleaner(memoryGcConfig, h.memoryDriver, h, taskMgr)
 	logger.GcLogger.With("type", "hybrid").Info("success initialize hybrid cleaners")
 }
 
@@ -139,46 +134,46 @@ func (h *hybridStorageMgr) getMemoryDefaultGcConfig() *storage.GCConfig {
 }
 
 type hybridStorageMgr struct {
-	cfg                *storage.Config
-	memoryDriver       storedriver.Driver
-	diskDriver         storedriver.Driver
-	diskStoreCleaner   *storage.Cleaner
-	memoryStoreCleaner *storage.Cleaner
-	taskMgr            mgr.SeedTaskMgr
-	shmSwitch          *shmSwitch
-	hasShm             bool
+	cfg                 *storage.Config
+	memoryDriver        storedriver.Driver
+	diskDriver          storedriver.Driver
+	diskDriverCleaner   *storage.Cleaner
+	memoryDriverCleaner *storage.Cleaner
+	taskMgr             daemon.SeedTaskMgr
+	shmSwitch           *shmSwitch
+	hasShm              bool
 }
 
-func (h *hybridStorageMgr) GC(ctx context.Context) error {
+func (h *hybridStorageMgr) GC() error {
 	logger.GcLogger.With("type", "hybrid").Info("start the hybrid storage gc job")
 	var wg sync.WaitGroup
 	wg.Add(1)
 	go func() {
 		defer wg.Done()
-		gcTaskIDs, err := h.diskStoreCleaner.GC("hybrid", false)
+		gcTaskIDs, err := h.diskDriverCleaner.GC("hybrid", false)
 		if err != nil {
 			logger.GcLogger.With("type", "hybrid").Error("gc disk: failed to get gcTaskIds")
 		}
-		realGCCount := h.gcTasks(ctx, gcTaskIDs, true)
+		realGCCount := h.gcTasks(gcTaskIDs, true)
 		logger.GcLogger.With("type", "hybrid").Infof("at most %d tasks can be cleaned up from disk, actual gc %d tasks", len(gcTaskIDs), realGCCount)
 	}()
 	if h.hasShm {
 		wg.Add(1)
 		go func() {
 			defer wg.Done()
-			gcTaskIDs, err := h.memoryStoreCleaner.GC("hybrid", false)
+			gcTaskIDs, err := h.memoryDriverCleaner.GC("hybrid", false)
 			logger.GcLogger.With("type", "hybrid").Infof("at most %d tasks can be cleaned up from memory", len(gcTaskIDs))
 			if err != nil {
 				logger.GcLogger.With("type", "hybrid").Error("gc memory: failed to get gcTaskIds")
 			}
-			h.gcTasks(ctx, gcTaskIDs, false)
+			h.gcTasks(gcTaskIDs, false)
 		}()
 	}
 	wg.Wait()
 	return nil
 }
 
-func (h *hybridStorageMgr) gcTasks(ctx context.Context, gcTaskIDs []string, isDisk bool) int {
+func (h *hybridStorageMgr) gcTasks(gcTaskIDs []string, isDisk bool) int {
 	var realGCCount int
 	for _, taskID := range gcTaskIDs {
 		synclock.Lock(taskID, false)
@@ -381,7 +376,7 @@ func (h *hybridStorageMgr) tryShmSpace(url, taskID string, fileLength int64) (st
 			fileLength)
 		if !canUseShm {
 			// 如果剩余空间过小，则强制执行一次fullgc后在检查是否满足
-			h.memoryStoreCleaner.GC("hybrid", true)
+			h.memoryDriverCleaner.GC("hybrid", true)
 			canUseShm = h.getMemoryUsableSpace()-unit.Bytes(remainder.Load())-secureLevel >= unit.Bytes(
 				fileLength)
 		}
diff --git a/cdnsystem/daemon/mgr/cdn/storage/hybrid/shm_switch.go b/cdnsystem/daemon/cdn/storage/hybrid/shm_switch.go
similarity index 100%
rename from cdnsystem/daemon/mgr/cdn/storage/hybrid/shm_switch.go
rename to cdnsystem/daemon/cdn/storage/hybrid/shm_switch.go
diff --git a/cdnsystem/daemon/cdn/storage/mock/mock_storage_mgr.go b/cdnsystem/daemon/cdn/storage/mock/mock_storage_mgr.go
new file mode 100644
index 00000000..b5354f1e
--- /dev/null
+++ b/cdnsystem/daemon/cdn/storage/mock/mock_storage_mgr.go
@@ -0,0 +1,210 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: d7y.io/dragonfly/v2/cdnsystem/daemon/mgr/cdn/storage (interfaces: Manager)
+
+// Package mock is a generated GoMock package.
+package mock
+
+import (
+	bytes "bytes"
+	io "io"
+	reflect "reflect"
+
+	"d7y.io/dragonfly/v2/cdnsystem/daemon"
+	storage "d7y.io/dragonfly/v2/cdnsystem/daemon/cdn/storage"
+	storedriver "d7y.io/dragonfly/v2/cdnsystem/storedriver"
+	types "d7y.io/dragonfly/v2/cdnsystem/types"
+	gomock "github.com/golang/mock/gomock"
+)
+
+// MockManager is a mock of Manager interface.
+type MockManager struct {
+	ctrl     *gomock.Controller
+	recorder *MockManagerMockRecorder
+}
+
+// MockManagerMockRecorder is the mock recorder for MockManager.
+type MockManagerMockRecorder struct {
+	mock *MockManager
+}
+
+// NewMockManager creates a new mock instance.
+func NewMockManager(ctrl *gomock.Controller) *MockManager {
+	mock := &MockManager{ctrl: ctrl}
+	mock.recorder = &MockManagerMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockManager) EXPECT() *MockManagerMockRecorder {
+	return m.recorder
+}
+
+// AppendPieceMetaData mocks base method.
+func (m *MockManager) AppendPieceMetaData(arg0 string, arg1 *storage.PieceMetaRecord) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "AppendPieceMetaData", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// AppendPieceMetaData indicates an expected call of AppendPieceMetaData.
+func (mr *MockManagerMockRecorder) AppendPieceMetaData(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AppendPieceMetaData", reflect.TypeOf((*MockManager)(nil).AppendPieceMetaData), arg0, arg1)
+}
+
+// CreateUploadLink mocks base method.
+func (m *MockManager) CreateUploadLink(arg0 string) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "CreateUploadLink", arg0)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// CreateUploadLink indicates an expected call of CreateUploadLink.
+func (mr *MockManagerMockRecorder) CreateUploadLink(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateUploadLink", reflect.TypeOf((*MockManager)(nil).CreateUploadLink), arg0)
+}
+
+// DeleteTask mocks base method.
+func (m *MockManager) DeleteTask(arg0 string) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "DeleteTask", arg0)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// DeleteTask indicates an expected call of DeleteTask.
+func (mr *MockManagerMockRecorder) DeleteTask(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteTask", reflect.TypeOf((*MockManager)(nil).DeleteTask), arg0)
+}
+
+// Initialize mocks base method.
+func (m *MockManager) Initialize(arg0 daemon.SeedTaskMgr) {
+	m.ctrl.T.Helper()
+	m.ctrl.Call(m, "Initialize", arg0)
+}
+
+// Initialize indicates an expected call of Initialize.
+func (mr *MockManagerMockRecorder) Initialize(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Initialize", reflect.TypeOf((*MockManager)(nil).Initialize), arg0)
+}
+
+// ReadDownloadFile mocks base method.
+func (m *MockManager) ReadDownloadFile(arg0 string) (io.ReadCloser, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "ReadDownloadFile", arg0)
+	ret0, _ := ret[0].(io.ReadCloser)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// ReadDownloadFile indicates an expected call of ReadDownloadFile.
+func (mr *MockManagerMockRecorder) ReadDownloadFile(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadDownloadFile", reflect.TypeOf((*MockManager)(nil).ReadDownloadFile), arg0)
+}
+
+// ReadFileMetaData mocks base method.
+func (m *MockManager) ReadFileMetaData(arg0 string) (*storage.FileMetaData, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "ReadFileMetaData", arg0)
+	ret0, _ := ret[0].(*storage.FileMetaData)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// ReadFileMetaData indicates an expected call of ReadFileMetaData.
+func (mr *MockManagerMockRecorder) ReadFileMetaData(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadFileMetaData", reflect.TypeOf((*MockManager)(nil).ReadFileMetaData), arg0)
+}
+
+// ReadPieceMetaRecords mocks base method.
+func (m *MockManager) ReadPieceMetaRecords(arg0 string) ([]*storage.PieceMetaRecord, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "ReadPieceMetaRecords", arg0)
+	ret0, _ := ret[0].([]*storage.PieceMetaRecord)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// ReadPieceMetaRecords indicates an expected call of ReadPieceMetaRecords.
+func (mr *MockManagerMockRecorder) ReadPieceMetaRecords(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadPieceMetaRecords", reflect.TypeOf((*MockManager)(nil).ReadPieceMetaRecords), arg0)
+}
+
+// ResetRepo mocks base method.
+func (m *MockManager) ResetRepo(arg0 *types.SeedTask) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "ResetRepo", arg0)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// ResetRepo indicates an expected call of ResetRepo.
+func (mr *MockManagerMockRecorder) ResetRepo(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResetRepo", reflect.TypeOf((*MockManager)(nil).ResetRepo), arg0)
+}
+
+// StatDownloadFile mocks base method.
+func (m *MockManager) StatDownloadFile(arg0 string) (*storedriver.StorageInfo, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "StatDownloadFile", arg0)
+	ret0, _ := ret[0].(*storedriver.StorageInfo)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// StatDownloadFile indicates an expected call of StatDownloadFile.
+func (mr *MockManagerMockRecorder) StatDownloadFile(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StatDownloadFile", reflect.TypeOf((*MockManager)(nil).StatDownloadFile), arg0)
+}
+
+// WriteDownloadFile mocks base method.
+func (m *MockManager) WriteDownloadFile(arg0 string, arg1, arg2 int64, arg3 *bytes.Buffer) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "WriteDownloadFile", arg0, arg1, arg2, arg3)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// WriteDownloadFile indicates an expected call of WriteDownloadFile.
+func (mr *MockManagerMockRecorder) WriteDownloadFile(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WriteDownloadFile", reflect.TypeOf((*MockManager)(nil).WriteDownloadFile), arg0, arg1, arg2, arg3)
+}
+
+// WriteFileMetaData mocks base method.
+func (m *MockManager) WriteFileMetaData(arg0 string, arg1 *storage.FileMetaData) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "WriteFileMetaData", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// WriteFileMetaData indicates an expected call of WriteFileMetaData.
+func (mr *MockManagerMockRecorder) WriteFileMetaData(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WriteFileMetaData", reflect.TypeOf((*MockManager)(nil).WriteFileMetaData), arg0, arg1)
+}
+
+// WritePieceMetaRecords mocks base method.
+func (m *MockManager) WritePieceMetaRecords(arg0 string, arg1 []*storage.PieceMetaRecord) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "WritePieceMetaRecords", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// WritePieceMetaRecords indicates an expected call of WritePieceMetaRecords.
+func (mr *MockManagerMockRecorder) WritePieceMetaRecords(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WritePieceMetaRecords", reflect.TypeOf((*MockManager)(nil).WritePieceMetaRecords), arg0, arg1)
+}
diff --git a/cdnsystem/daemon/mgr/cdn/storage/path_util.go b/cdnsystem/daemon/cdn/storage/path_util.go
similarity index 100%
rename from cdnsystem/daemon/mgr/cdn/storage/path_util.go
rename to cdnsystem/daemon/cdn/storage/path_util.go
diff --git a/cdnsystem/daemon/mgr/cdn/storage/storage_gc.go b/cdnsystem/daemon/cdn/storage/storage_gc.go
similarity index 96%
rename from cdnsystem/daemon/mgr/cdn/storage/storage_gc.go
rename to cdnsystem/daemon/cdn/storage/storage_gc.go
index f363a4f7..c221bd10 100644
--- a/cdnsystem/daemon/mgr/cdn/storage/storage_gc.go
+++ b/cdnsystem/daemon/cdn/storage/storage_gc.go
@@ -21,8 +21,8 @@ import (
 	"strings"
 	"time"
 
-	"d7y.io/dragonfly/v2/cdnsystem/cdnerrors"
-	"d7y.io/dragonfly/v2/cdnsystem/daemon/mgr"
+	"d7y.io/dragonfly/v2/cdnsystem/daemon"
+	cdnerrors "d7y.io/dragonfly/v2/cdnsystem/errors"
 	"d7y.io/dragonfly/v2/cdnsystem/storedriver"
 	logger "d7y.io/dragonfly/v2/pkg/dflog"
 	"d7y.io/dragonfly/v2/pkg/util/timeutils"
@@ -34,11 +34,11 @@ import (
 type Cleaner struct {
 	cfg        *GCConfig
 	driver     storedriver.Driver
-	taskMgr    mgr.SeedTaskMgr
+	taskMgr    daemon.SeedTaskMgr
 	storageMgr Manager
 }
 
-func NewStorageCleaner(cfg *GCConfig, driver storedriver.Driver, storageMgr Manager, taskMgr mgr.SeedTaskMgr) (*Cleaner, error) {
+func NewStorageCleaner(cfg *GCConfig, driver storedriver.Driver, storageMgr Manager, taskMgr daemon.SeedTaskMgr) (*Cleaner, error) {
 	return &Cleaner{
 		cfg:        cfg,
 		driver:     driver,
diff --git a/cdnsystem/daemon/mgr/cdn/storage/storage_mgr.go b/cdnsystem/daemon/cdn/storage/storage_mgr.go
similarity index 93%
rename from cdnsystem/daemon/mgr/cdn/storage/storage_mgr.go
rename to cdnsystem/daemon/cdn/storage/storage_mgr.go
index 07facf93..45715b21 100644
--- a/cdnsystem/daemon/mgr/cdn/storage/storage_mgr.go
+++ b/cdnsystem/daemon/cdn/storage/storage_mgr.go
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+//go:generate mockgen -destination ./mock/mock_storage_mgr.go -package mock d7y.io/dragonfly/v2/cdnsystem/daemon/mgr/cdn/storage Manager
 
 package storage
 
@@ -25,7 +26,7 @@ import (
 	"strings"
 	"time"
 
-	"d7y.io/dragonfly/v2/cdnsystem/daemon/mgr"
+	"d7y.io/dragonfly/v2/cdnsystem/daemon"
 	"d7y.io/dragonfly/v2/cdnsystem/plugins"
 	"d7y.io/dragonfly/v2/cdnsystem/storedriver"
 	"d7y.io/dragonfly/v2/cdnsystem/types"
@@ -37,7 +38,7 @@ import (
 )
 
 type Manager interface {
-	Initialize(taskMgr mgr.SeedTaskMgr)
+	Initialize(taskMgr daemon.SeedTaskMgr)
 
 	// ResetRepo reset the storage of task
 	ResetRepo(*types.SeedTask) error
@@ -210,7 +211,7 @@ type ManagerBuilder func(cfg *Config) (Manager, error)
 
 // Register defines an interface to register a storage manager with specified name.
 // All storage managers should call this function to register itself to the storage manager factory.
-func Register(name string, builder ManagerBuilder) {
+func Register(name string, builder ManagerBuilder) error {
 	name = strings.ToLower(name)
 	// plugin builder
 	var f = func(conf interface{}) (plugins.Plugin, error) {
@@ -241,7 +242,7 @@ func Register(name string, builder ManagerBuilder) {
 		}
 		return newManagerPlugin(name, builder, cfg)
 	}
-	plugins.RegisterPluginBuilder(plugins.StorageManagerPlugin, name, f)
+	return plugins.RegisterPluginBuilder(plugins.StorageManagerPlugin, name, f)
 }
 
 func newManagerPlugin(name string, builder ManagerBuilder, cfg *Config) (plugins.Plugin, error) {
@@ -261,15 +262,12 @@ func newManagerPlugin(name string, builder ManagerBuilder, cfg *Config) (plugins
 }
 
 // Get a storage manager from manager with specified name.
-func Get(name string) (Manager, error) {
-	v := plugins.GetPlugin(plugins.StorageManagerPlugin, strings.ToLower(name))
-	if v == nil {
-		return nil, fmt.Errorf("storage manager: %s not existed", name)
+func Get(name string) (Manager, bool) {
+	v, ok := plugins.GetPlugin(plugins.StorageManagerPlugin, strings.ToLower(name))
+	if !ok {
+		return nil, false
 	}
-	if plugin, ok := v.(*managerPlugin); ok {
-		return plugin.instance, nil
-	}
-	return nil, fmt.Errorf("get store manager %s storage error: unknown reason", name)
+	return v.(*managerPlugin).instance, true
 }
 
 type Config struct {
@@ -289,3 +287,8 @@ type GCConfig struct {
 	CleanRatio        int           `yaml:"cleanRatio"`
 	IntervalThreshold time.Duration `yaml:"intervalThreshold"`
 }
+
+const (
+	HybridStorageMode = "hybrid"
+	DiskStorageMode   = "disk"
+)
diff --git a/cdnsystem/daemon/mgr/cdn_mgr.go b/cdnsystem/daemon/cdn_mgr.go
similarity index 87%
rename from cdnsystem/daemon/mgr/cdn_mgr.go
rename to cdnsystem/daemon/cdn_mgr.go
index d4d49b78..e77046d7 100644
--- a/cdnsystem/daemon/mgr/cdn_mgr.go
+++ b/cdnsystem/daemon/cdn_mgr.go
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-package mgr
+//go:generate mockgen -destination ./mock/mock_cdn_mgr.go -package mock d7y.io/dragonfly/v2/cdnsystem/daemon/mgr CDNMgr
+
+package daemon
 
 import (
 	"context"
@@ -31,5 +33,5 @@ type CDNMgr interface {
 
 	// Delete the cdn meta with specified taskID.
 	// The file on the disk will be deleted when the force is true.
-	Delete(context.Context, string) error
+	Delete(string) error
 }
diff --git a/cdnsystem/daemon/mgr/gc/manager.go b/cdnsystem/daemon/gc/manager.go
similarity index 65%
rename from cdnsystem/daemon/mgr/gc/manager.go
rename to cdnsystem/daemon/gc/manager.go
index 32c51a01..af965c6e 100644
--- a/cdnsystem/daemon/mgr/gc/manager.go
+++ b/cdnsystem/daemon/gc/manager.go
@@ -18,21 +18,15 @@ package gc
 
 import (
 	"context"
+	"strings"
 	"sync"
 	"time"
 
-	"d7y.io/dragonfly/v2/cdnsystem/daemon/mgr"
 	logger "d7y.io/dragonfly/v2/pkg/dflog"
 )
 
-func init() {
-	// Ensure that Manager implements the GCMgr interface
-	var manager *Manager = nil
-	var _ mgr.GCMgr = manager
-}
-
 type Executor interface {
-	GC(ctx context.Context) error
+	GC() error
 }
 
 type ExecutorWrapper struct {
@@ -47,42 +41,15 @@ var (
 
 // Register a gc task
 func Register(name string, gcInitialDelay time.Duration, gcInterval time.Duration, gcExecutor Executor) {
-	gcExecutorWrappers[name] = &ExecutorWrapper{
+	gcExecutorWrappers[strings.ToLower(name)] = &ExecutorWrapper{
 		gcInitialDelay: gcInitialDelay,
 		gcInterval:     gcInterval,
 		gcExecutor:     gcExecutor,
 	}
 }
 
-// Manager is an implementation of the interface of GCMgr.
-type Manager struct {
-	taskMgr mgr.SeedTaskMgr
-	cdnMgr  mgr.CDNMgr
-}
-
-func (gcm *Manager) GCTask(ctx context.Context, taskID string, full bool) error {
-	// todo data consistency
-	var err error
-	if full {
-		err = gcm.cdnMgr.Delete(ctx, taskID)
-		if err != nil {
-			return err
-		}
-	}
-	err = gcm.taskMgr.Delete(ctx, taskID)
-	return err
-}
-
-// NewManager returns a new Manager.
-func NewManager(taskMgr mgr.SeedTaskMgr, cdnMgr mgr.CDNMgr) (*Manager, error) {
-	return &Manager{
-		taskMgr: taskMgr,
-		cdnMgr:  cdnMgr,
-	}, nil
-}
-
 // StartGC starts to do the gc jobs.
-func (gcm *Manager) StartGC(ctx context.Context) error {
+func StartGC(ctx context.Context) error {
 	logger.Debugf("====start the gc jobs====")
 	var wg sync.WaitGroup
 	for name, executorWrapper := range gcExecutorWrappers {
@@ -90,7 +57,7 @@ func (gcm *Manager) StartGC(ctx context.Context) error {
 		// start a goroutine to gc
 		go func(name string, wrapper *ExecutorWrapper) {
 			logger.Debugf("start the %s gc task", name)
-			// delay to execute GC after gcm.initialDelay
+			// delay to execute GC after initialDelay
 			time.Sleep(wrapper.gcInitialDelay)
 			wg.Done()
 			// execute the GC by fixed delay
@@ -101,7 +68,7 @@ func (gcm *Manager) StartGC(ctx context.Context) error {
 					logger.Infof("exit %s gc task", name)
 					return
 				case <-ticker.C:
-					if err := wrapper.gcExecutor.GC(ctx); err != nil {
+					if err := wrapper.gcExecutor.GC(); err != nil {
 						logger.Errorf("%s gc task execute failed: %v", name, err)
 					}
 				}
diff --git a/cdnsystem/daemon/mock/mock_cdn_mgr.go b/cdnsystem/daemon/mock/mock_cdn_mgr.go
new file mode 100644
index 00000000..78524402
--- /dev/null
+++ b/cdnsystem/daemon/mock/mock_cdn_mgr.go
@@ -0,0 +1,65 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: d7y.io/dragonfly/v2/cdnsystem/daemon/mgr (interfaces: CDNMgr)
+
+// Package mock is a generated GoMock package.
+package mock
+
+import (
+	context "context"
+	reflect "reflect"
+
+	types "d7y.io/dragonfly/v2/cdnsystem/types"
+	gomock "github.com/golang/mock/gomock"
+)
+
+// MockCDNMgr is a mock of CDNMgr interface.
+type MockCDNMgr struct {
+	ctrl     *gomock.Controller
+	recorder *MockCDNMgrMockRecorder
+}
+
+// MockCDNMgrMockRecorder is the mock recorder for MockCDNMgr.
+type MockCDNMgrMockRecorder struct {
+	mock *MockCDNMgr
+}
+
+// NewMockCDNMgr creates a new mock instance.
+func NewMockCDNMgr(ctrl *gomock.Controller) *MockCDNMgr {
+	mock := &MockCDNMgr{ctrl: ctrl}
+	mock.recorder = &MockCDNMgrMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockCDNMgr) EXPECT() *MockCDNMgrMockRecorder {
+	return m.recorder
+}
+
+// Delete mocks base method.
+func (m *MockCDNMgr) Delete(arg0 string) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Delete", arg0)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// Delete indicates an expected call of Delete.
+func (mr *MockCDNMgrMockRecorder) Delete(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockCDNMgr)(nil).Delete), arg0)
+}
+
+// TriggerCDN mocks base method.
+func (m *MockCDNMgr) TriggerCDN(arg0 context.Context, arg1 *types.SeedTask) (*types.SeedTask, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "TriggerCDN", arg0, arg1)
+	ret0, _ := ret[0].(*types.SeedTask)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// TriggerCDN indicates an expected call of TriggerCDN.
+func (mr *MockCDNMgrMockRecorder) TriggerCDN(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TriggerCDN", reflect.TypeOf((*MockCDNMgr)(nil).TriggerCDN), arg0, arg1)
+}
diff --git a/cdnsystem/daemon/mock/mock_gc_mgr.go b/cdnsystem/daemon/mock/mock_gc_mgr.go
new file mode 100644
index 00000000..c7de819d
--- /dev/null
+++ b/cdnsystem/daemon/mock/mock_gc_mgr.go
@@ -0,0 +1,63 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: d7y.io/dragonfly/v2/cdnsystem/daemon/mgr (interfaces: GCMgr)
+
+// Package mock is a generated GoMock package.
+package mock
+
+import (
+	context "context"
+	reflect "reflect"
+
+	gomock "github.com/golang/mock/gomock"
+)
+
+// MockGCMgr is a mock of GCMgr interface.
+type MockGCMgr struct {
+	ctrl     *gomock.Controller
+	recorder *MockGCMgrMockRecorder
+}
+
+// MockGCMgrMockRecorder is the mock recorder for MockGCMgr.
+type MockGCMgrMockRecorder struct {
+	mock *MockGCMgr
+}
+
+// NewMockGCMgr creates a new mock instance.
+func NewMockGCMgr(ctrl *gomock.Controller) *MockGCMgr {
+	mock := &MockGCMgr{ctrl: ctrl}
+	mock.recorder = &MockGCMgrMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockGCMgr) EXPECT() *MockGCMgrMockRecorder {
+	return m.recorder
+}
+
+// GCTask mocks base method.
+func (m *MockGCMgr) GCTask(arg0 string, arg1 bool) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GCTask", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// GCTask indicates an expected call of GCTask.
+func (mr *MockGCMgrMockRecorder) GCTask(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GCTask", reflect.TypeOf((*MockGCMgr)(nil).GCTask), arg0, arg1)
+}
+
+// StartGC mocks base method.
+func (m *MockGCMgr) StartGC(arg0 context.Context) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "StartGC", arg0)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// StartGC indicates an expected call of StartGC.
+func (mr *MockGCMgrMockRecorder) StartGC(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartGC", reflect.TypeOf((*MockGCMgr)(nil).StartGC), arg0)
+}
diff --git a/cdnsystem/daemon/mock/mock_progress_mgr.go b/cdnsystem/daemon/mock/mock_progress_mgr.go
new file mode 100644
index 00000000..d1dae6d8
--- /dev/null
+++ b/cdnsystem/daemon/mock/mock_progress_mgr.go
@@ -0,0 +1,133 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: d7y.io/dragonfly/v2/cdnsystem/daemon/mgr (interfaces: SeedProgressMgr)
+
+// Package mock is a generated GoMock package.
+package mock
+
+import (
+	context "context"
+	reflect "reflect"
+
+	"d7y.io/dragonfly/v2/cdnsystem/daemon"
+	types "d7y.io/dragonfly/v2/cdnsystem/types"
+	gomock "github.com/golang/mock/gomock"
+)
+
+// MockSeedProgressMgr is a mock of SeedProgressMgr interface.
+type MockSeedProgressMgr struct {
+	ctrl     *gomock.Controller
+	recorder *MockSeedProgressMgrMockRecorder
+}
+
+// MockSeedProgressMgrMockRecorder is the mock recorder for MockSeedProgressMgr.
+type MockSeedProgressMgrMockRecorder struct {
+	mock *MockSeedProgressMgr
+}
+
+// NewMockSeedProgressMgr creates a new mock instance.
+func NewMockSeedProgressMgr(ctrl *gomock.Controller) *MockSeedProgressMgr {
+	mock := &MockSeedProgressMgr{ctrl: ctrl}
+	mock.recorder = &MockSeedProgressMgrMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockSeedProgressMgr) EXPECT() *MockSeedProgressMgrMockRecorder {
+	return m.recorder
+}
+
+// Clear mocks base method.
+func (m *MockSeedProgressMgr) Clear(arg0 string) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Clear", arg0)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// Clear indicates an expected call of Clear.
+func (mr *MockSeedProgressMgrMockRecorder) Clear(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clear", reflect.TypeOf((*MockSeedProgressMgr)(nil).Clear), arg0)
+}
+
+// GetPieces mocks base method.
+func (m *MockSeedProgressMgr) GetPieces(arg0 context.Context, arg1 string) ([]*types.SeedPiece, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetPieces", arg0, arg1)
+	ret0, _ := ret[0].([]*types.SeedPiece)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetPieces indicates an expected call of GetPieces.
+func (mr *MockSeedProgressMgrMockRecorder) GetPieces(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPieces", reflect.TypeOf((*MockSeedProgressMgr)(nil).GetPieces), arg0, arg1)
+}
+
+// InitSeedProgress mocks base method.
+func (m *MockSeedProgressMgr) InitSeedProgress(arg0 context.Context, arg1 string) {
+	m.ctrl.T.Helper()
+	m.ctrl.Call(m, "InitSeedProgress", arg0, arg1)
+}
+
+// InitSeedProgress indicates an expected call of InitSeedProgress.
+func (mr *MockSeedProgressMgrMockRecorder) InitSeedProgress(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InitSeedProgress", reflect.TypeOf((*MockSeedProgressMgr)(nil).InitSeedProgress), arg0, arg1)
+}
+
+// PublishPiece mocks base method.
+func (m *MockSeedProgressMgr) PublishPiece(arg0 context.Context, arg1 string, arg2 *types.SeedPiece) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "PublishPiece", arg0, arg1, arg2)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// PublishPiece indicates an expected call of PublishPiece.
+func (mr *MockSeedProgressMgrMockRecorder) PublishPiece(arg0, arg1, arg2 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PublishPiece", reflect.TypeOf((*MockSeedProgressMgr)(nil).PublishPiece), arg0, arg1, arg2)
+}
+
+// PublishTask mocks base method.
+func (m *MockSeedProgressMgr) PublishTask(arg0 context.Context, arg1 string, arg2 *types.SeedTask) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "PublishTask", arg0, arg1, arg2)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// PublishTask indicates an expected call of PublishTask.
+func (mr *MockSeedProgressMgrMockRecorder) PublishTask(arg0, arg1, arg2 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PublishTask", reflect.TypeOf((*MockSeedProgressMgr)(nil).PublishTask), arg0, arg1, arg2)
+}
+
+// SetTaskMgr mocks base method.
+func (m *MockSeedProgressMgr) SetTaskMgr(arg0 daemon.SeedTaskMgr) {
+	m.ctrl.T.Helper()
+	m.ctrl.Call(m, "SetTaskMgr", arg0)
+}
+
+// SetTaskMgr indicates an expected call of SetTaskMgr.
+func (mr *MockSeedProgressMgrMockRecorder) SetTaskMgr(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetTaskMgr", reflect.TypeOf((*MockSeedProgressMgr)(nil).SetTaskMgr), arg0)
+}
+
+// WatchSeedProgress mocks base method.
+func (m *MockSeedProgressMgr) WatchSeedProgress(arg0 context.Context, arg1 string) (<-chan *types.SeedPiece, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "WatchSeedProgress", arg0, arg1)
+	ret0, _ := ret[0].(<-chan *types.SeedPiece)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// WatchSeedProgress indicates an expected call of WatchSeedProgress.
+func (mr *MockSeedProgressMgrMockRecorder) WatchSeedProgress(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WatchSeedProgress", reflect.TypeOf((*MockSeedProgressMgr)(nil).WatchSeedProgress), arg0, arg1)
+}
diff --git a/cdnsystem/daemon/mock/mock_task_mgr.go b/cdnsystem/daemon/mock/mock_task_mgr.go
new file mode 100644
index 00000000..1833bb3e
--- /dev/null
+++ b/cdnsystem/daemon/mock/mock_task_mgr.go
@@ -0,0 +1,111 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: d7y.io/dragonfly/v2/cdnsystem/daemon/mgr (interfaces: SeedTaskMgr)
+
+// Package mock is a generated GoMock package.
+package mock
+
+import (
+	context "context"
+	reflect "reflect"
+
+	types "d7y.io/dragonfly/v2/cdnsystem/types"
+	syncmap "d7y.io/dragonfly/v2/pkg/structure/syncmap"
+	gomock "github.com/golang/mock/gomock"
+)
+
+// MockSeedTaskMgr is a mock of SeedTaskMgr interface.
+type MockSeedTaskMgr struct {
+	ctrl     *gomock.Controller
+	recorder *MockSeedTaskMgrMockRecorder
+}
+
+// MockSeedTaskMgrMockRecorder is the mock recorder for MockSeedTaskMgr.
+type MockSeedTaskMgrMockRecorder struct {
+	mock *MockSeedTaskMgr
+}
+
+// NewMockSeedTaskMgr creates a new mock instance.
+func NewMockSeedTaskMgr(ctrl *gomock.Controller) *MockSeedTaskMgr {
+	mock := &MockSeedTaskMgr{ctrl: ctrl}
+	mock.recorder = &MockSeedTaskMgrMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockSeedTaskMgr) EXPECT() *MockSeedTaskMgrMockRecorder {
+	return m.recorder
+}
+
+// Delete mocks base method.
+func (m *MockSeedTaskMgr) Delete(arg0 string) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Delete", arg0)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// Delete indicates an expected call of Delete.
+func (mr *MockSeedTaskMgrMockRecorder) Delete(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockSeedTaskMgr)(nil).Delete), arg0)
+}
+
+// Get mocks base method.
+func (m *MockSeedTaskMgr) Get(arg0 string) (*types.SeedTask, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Get", arg0)
+	ret0, _ := ret[0].(*types.SeedTask)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// Get indicates an expected call of Get.
+func (mr *MockSeedTaskMgrMockRecorder) Get(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockSeedTaskMgr)(nil).Get), arg0)
+}
+
+// GetAccessTime mocks base method.
+func (m *MockSeedTaskMgr) GetAccessTime() (*syncmap.SyncMap, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetAccessTime")
+	ret0, _ := ret[0].(*syncmap.SyncMap)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetAccessTime indicates an expected call of GetAccessTime.
+func (mr *MockSeedTaskMgrMockRecorder) GetAccessTime() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccessTime", reflect.TypeOf((*MockSeedTaskMgr)(nil).GetAccessTime))
+}
+
+// GetPieces mocks base method.
+func (m *MockSeedTaskMgr) GetPieces(arg0 context.Context, arg1 string) ([]*types.SeedPiece, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetPieces", arg0, arg1)
+	ret0, _ := ret[0].([]*types.SeedPiece)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetPieces indicates an expected call of GetPieces.
+func (mr *MockSeedTaskMgrMockRecorder) GetPieces(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPieces", reflect.TypeOf((*MockSeedTaskMgr)(nil).GetPieces), arg0, arg1)
+}
+
+// Register mocks base method.
+func (m *MockSeedTaskMgr) Register(arg0 context.Context, arg1 *types.TaskRegisterRequest) (<-chan *types.SeedPiece, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Register", arg0, arg1)
+	ret0, _ := ret[0].(<-chan *types.SeedPiece)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// Register indicates an expected call of Register.
+func (mr *MockSeedTaskMgrMockRecorder) Register(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Register", reflect.TypeOf((*MockSeedTaskMgr)(nil).Register), arg0, arg1)
+}
diff --git a/cdnsystem/daemon/mgr/progress/manager.go b/cdnsystem/daemon/progress/manager.go
similarity index 93%
rename from cdnsystem/daemon/mgr/progress/manager.go
rename to cdnsystem/daemon/progress/manager.go
index e3917d26..220dbc1d 100644
--- a/cdnsystem/daemon/mgr/progress/manager.go
+++ b/cdnsystem/daemon/progress/manager.go
@@ -22,7 +22,7 @@ import (
 	"sync"
 	"time"
 
-	"d7y.io/dragonfly/v2/cdnsystem/daemon/mgr"
+	"d7y.io/dragonfly/v2/cdnsystem/daemon"
 	"d7y.io/dragonfly/v2/cdnsystem/types"
 	"d7y.io/dragonfly/v2/pkg/dferrors"
 	logger "d7y.io/dragonfly/v2/pkg/dflog"
@@ -31,26 +31,22 @@ import (
 	"github.com/pkg/errors"
 )
 
-func init() {
-	// Ensure that Manager implements the SeedProgressMgr interface
-	var manager *Manager = nil
-	var _ mgr.SeedProgressMgr = manager
-}
+var _ daemon.SeedProgressMgr = (*Manager)(nil)
 
 type Manager struct {
 	seedSubscribers      *syncmap.SyncMap
 	taskPieceMetaRecords *syncmap.SyncMap
-	taskMgr              mgr.SeedTaskMgr
+	taskMgr              daemon.SeedTaskMgr
 	mu                   *synclock.LockerPool
 	timeout              time.Duration
 	buffer               int
 }
 
-func (pm *Manager) SetTaskMgr(taskMgr mgr.SeedTaskMgr) {
+func (pm *Manager) SetTaskMgr(taskMgr daemon.SeedTaskMgr) {
 	pm.taskMgr = taskMgr
 }
 
-func NewManager() (mgr.SeedProgressMgr, error) {
+func NewManager() (daemon.SeedProgressMgr, error) {
 	return &Manager{
 		seedSubscribers:      syncmap.NewSyncMap(),
 		taskPieceMetaRecords: syncmap.NewSyncMap(),
@@ -162,7 +158,7 @@ func (pm *Manager) PublishTask(ctx context.Context, taskID string, task *types.S
 	return nil
 }
 
-func (pm *Manager) Clear(ctx context.Context, taskID string) error {
+func (pm *Manager) Clear(taskID string) error {
 	pm.mu.Lock(taskID, false)
 	defer pm.mu.UnLock(taskID, false)
 	chanList, err := pm.seedSubscribers.GetAsList(taskID)
diff --git a/cdnsystem/daemon/mgr/progress/manager_util.go b/cdnsystem/daemon/progress/manager_util.go
similarity index 100%
rename from cdnsystem/daemon/mgr/progress/manager_util.go
rename to cdnsystem/daemon/progress/manager_util.go
diff --git a/cdnsystem/daemon/mgr/progress_mgr.go b/cdnsystem/daemon/progress_mgr.go
similarity index 88%
rename from cdnsystem/daemon/mgr/progress_mgr.go
rename to cdnsystem/daemon/progress_mgr.go
index bb498ada..ec5d98b8 100644
--- a/cdnsystem/daemon/mgr/progress_mgr.go
+++ b/cdnsystem/daemon/progress_mgr.go
@@ -13,8 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+//go:generate mockgen -destination ./mock/mock_progress_mgr.go -package mock d7y.io/dragonfly/v2/cdnsystem/daemon/mgr SeedProgressMgr
 
-package mgr
+package daemon
 
 import (
 	"context"
@@ -41,7 +42,7 @@ type SeedProgressMgr interface {
 	GetPieces(context.Context, string) (records []*types.SeedPiece, err error)
 
 	// Clear meta info of task
-	Clear(context.Context, string) error
+	Clear(string) error
 
 	SetTaskMgr(taskMgr SeedTaskMgr)
 }
diff --git a/cdnsystem/daemon/mgr/task/manager.go b/cdnsystem/daemon/task/manager.go
similarity index 90%
rename from cdnsystem/daemon/mgr/task/manager.go
rename to cdnsystem/daemon/task/manager.go
index 4da75ac4..a2285728 100644
--- a/cdnsystem/daemon/mgr/task/manager.go
+++ b/cdnsystem/daemon/task/manager.go
@@ -21,11 +21,10 @@ import (
 	"fmt"
 	"time"
 
-	"d7y.io/dragonfly/v2/cdnsystem/cdnerrors"
 	"d7y.io/dragonfly/v2/cdnsystem/config"
-	"d7y.io/dragonfly/v2/cdnsystem/daemon/mgr"
-	"d7y.io/dragonfly/v2/cdnsystem/daemon/mgr/gc"
-	"d7y.io/dragonfly/v2/cdnsystem/source"
+	"d7y.io/dragonfly/v2/cdnsystem/daemon"
+	"d7y.io/dragonfly/v2/cdnsystem/daemon/gc"
+	cdnerrors "d7y.io/dragonfly/v2/cdnsystem/errors"
 	"d7y.io/dragonfly/v2/cdnsystem/types"
 	"d7y.io/dragonfly/v2/pkg/dferrors"
 	logger "d7y.io/dragonfly/v2/pkg/dflog"
@@ -36,7 +35,7 @@ import (
 )
 
 // Ensure that Manager implements the SeedTaskMgr and gcExecutor interfaces
-var _ mgr.SeedTaskMgr = (*Manager)(nil)
+var _ daemon.SeedTaskMgr = (*Manager)(nil)
 var _ gc.Executor = (*Manager)(nil)
 
 // Manager is an implementation of the interface of TaskMgr.
@@ -45,19 +44,17 @@ type Manager struct {
 	taskStore               *syncmap.SyncMap
 	accessTimeMap           *syncmap.SyncMap
 	taskURLUnReachableStore *syncmap.SyncMap
-	resourceClient          source.ResourceClient
-	cdnMgr                  mgr.CDNMgr
-	progressMgr             mgr.SeedProgressMgr
+	cdnMgr                  daemon.CDNMgr
+	progressMgr             daemon.SeedProgressMgr
 }
 
 // NewManager returns a new Manager Object.
-func NewManager(cfg *config.Config, cdnMgr mgr.CDNMgr, progressMgr mgr.SeedProgressMgr, resourceClient source.ResourceClient) (*Manager, error) {
+func NewManager(cfg *config.Config, cdnMgr daemon.CDNMgr, progressMgr daemon.SeedProgressMgr) (*Manager, error) {
 	taskMgr := &Manager{
 		cfg:                     cfg,
 		taskStore:               syncmap.NewSyncMap(),
 		accessTimeMap:           syncmap.NewSyncMap(),
 		taskURLUnReachableStore: syncmap.NewSyncMap(),
-		resourceClient:          resourceClient,
 		cdnMgr:                  cdnMgr,
 		progressMgr:             progressMgr,
 	}
@@ -168,11 +165,11 @@ func (tm Manager) GetAccessTime() (*syncmap.SyncMap, error) {
 	return tm.accessTimeMap, nil
 }
 
-func (tm Manager) Delete(ctx context.Context, taskID string) error {
+func (tm Manager) Delete(taskID string) error {
 	tm.accessTimeMap.Delete(taskID)
 	tm.taskURLUnReachableStore.Delete(taskID)
 	tm.taskStore.Delete(taskID)
-	tm.progressMgr.Clear(ctx, taskID)
+	tm.progressMgr.Clear(taskID)
 	return nil
 }
 
@@ -188,7 +185,7 @@ const (
 	gcTasksTimeout = 2.0 * time.Second
 )
 
-func (tm *Manager) GC(ctx context.Context) error {
+func (tm *Manager) GC() error {
 	logger.Debugf("start the task meta gc job")
 	var removedTaskCount int
 	startTime := time.Now()
@@ -212,7 +209,7 @@ func (tm *Manager) GC(ctx context.Context) error {
 		}
 		// gc task memory data
 		logger.GcLogger.With("type", "meta").Infof("gc task: start to deal with task: %s", taskID)
-		tm.Delete(ctx, taskID)
+		tm.Delete(taskID)
 		removedTaskCount++
 	}
 
diff --git a/cdnsystem/daemon/mgr/task/manager_util.go b/cdnsystem/daemon/task/manager_util.go
similarity index 96%
rename from cdnsystem/daemon/mgr/task/manager_util.go
rename to cdnsystem/daemon/task/manager_util.go
index 8b516232..4a34dd64 100644
--- a/cdnsystem/daemon/mgr/task/manager_util.go
+++ b/cdnsystem/daemon/task/manager_util.go
@@ -20,10 +20,11 @@ import (
 	"context"
 	"time"
 
-	"d7y.io/dragonfly/v2/cdnsystem/cdnerrors"
 	"d7y.io/dragonfly/v2/cdnsystem/config"
+	cdnerrors "d7y.io/dragonfly/v2/cdnsystem/errors"
 	"d7y.io/dragonfly/v2/cdnsystem/types"
 	logger "d7y.io/dragonfly/v2/pkg/dflog"
+	"d7y.io/dragonfly/v2/pkg/source"
 	"d7y.io/dragonfly/v2/pkg/synclock"
 	"d7y.io/dragonfly/v2/pkg/util/net/urlutils"
 	"d7y.io/dragonfly/v2/pkg/util/stringutils"
@@ -80,7 +81,9 @@ func (tm *Manager) addOrUpdateTask(ctx context.Context, request *types.TaskRegis
 	}
 
 	// get sourceContentLength with req.Header
-	sourceFileLength, err := tm.resourceClient.GetContentLength(task.URL, request.Header)
+	ctx, cancel := context.WithTimeout(ctx, 4*time.Second)
+	defer cancel()
+	sourceFileLength, err := source.GetContentLength(ctx, task.URL, request.Header)
 	if err != nil {
 		logger.WithTaskID(task.TaskID).Errorf("failed to get url (%s) content length: %v", task.URL, err)
 
diff --git a/cdnsystem/daemon/mgr/task_mgr.go b/cdnsystem/daemon/task_mgr.go
similarity index 89%
rename from cdnsystem/daemon/mgr/task_mgr.go
rename to cdnsystem/daemon/task_mgr.go
index c0ab4148..139aec72 100644
--- a/cdnsystem/daemon/mgr/task_mgr.go
+++ b/cdnsystem/daemon/task_mgr.go
@@ -13,8 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+//go:generate mockgen -destination ./mock/mock_task_mgr.go -package mock d7y.io/dragonfly/v2/cdnsystem/daemon/mgr SeedTaskMgr
 
-package mgr
+package daemon
 
 import (
 	"context"
@@ -38,7 +39,7 @@ type SeedTaskMgr interface {
 	GetAccessTime() (*syncmap.SyncMap, error)
 
 	// Delete delete a task.
-	Delete(context.Context, string) error
+	Delete(string) error
 
 	// GetPieces
 	GetPieces(context.Context, string) (pieces []*types.SeedPiece, err error)
diff --git a/cdnsystem/cdnerrors/errors.go b/cdnsystem/errors/errors.go
similarity index 84%
rename from cdnsystem/cdnerrors/errors.go
rename to cdnsystem/errors/errors.go
index cd073354..81fbbe98 100644
--- a/cdnsystem/cdnerrors/errors.go
+++ b/cdnsystem/errors/errors.go
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package cdnerrors
+package errors
 
 import (
 	"github.com/pkg/errors"
@@ -30,22 +30,22 @@ var (
 	// ErrTaskIDDuplicate represents the task id is in conflict.
 	ErrTaskIDDuplicate = errors.New("taskId conflict")
 
-	// ErrPieceCountNotEqual represents piece count is not equal.
+	// ErrPieceCountNotEqual represents the number of pieces downloaded does not match the amount of meta information
 	ErrPieceCountNotEqual = errors.New("inconsistent number of pieces")
 
-	// ErrFileLengthNotEqual represents file length is not equal.
+	// ErrFileLengthNotEqual represents the file length of downloaded dose not match the length of meta information
 	ErrFileLengthNotEqual = errors.New("inconsistent file length")
 
-	// ErrDownloadFail represents download failed.
+	// ErrDownloadFail represents an exception was encountered while downloading the file
 	ErrDownloadFail = errors.New("resource download failed")
 
-	// ErrResourceExpired represents resource is expired.
+	// ErrResourceExpired represents the downloaded resource has expired
 	ErrResourceExpired = errors.New("resource expired")
 
-	// ErrResourceNotSupportRangeRequest represents resource is not support range.
+	// ErrResourceNotSupportRangeRequest represents the downloaded resource does not support Range downloads
 	ErrResourceNotSupportRangeRequest = errors.New("resource does not support range request")
 
-	// ErrPieceMd5NotMatch represents piece md5 is not match.
+	// ErrPieceMd5NotMatch represents the MD5 value of the download file is inconsistent with the meta information
 	ErrPieceMd5NotMatch = errors.New("piece md5 check fail")
 
 	// ErrDataNotFound represents the data cannot be found.
diff --git a/cdnsystem/plugins/plugin_manager.go b/cdnsystem/plugins/plugin_manager.go
index cf602e95..f8b93759 100644
--- a/cdnsystem/plugins/plugin_manager.go
+++ b/cdnsystem/plugins/plugin_manager.go
@@ -17,6 +17,7 @@
 package plugins
 
 import (
+	"fmt"
 	"sync"
 )
 
@@ -38,19 +39,19 @@ func NewRepository() Repository {
 // Manager manages all plugin builders and plugin instants.
 type Manager interface {
 	// GetBuilder adds a Builder object with the giving plugin type and name.
-	AddBuilder(pt PluginType, name string, b Builder)
+	AddBuilder(pt PluginType, name string, b Builder) error
 
 	// GetBuilder returns a Builder object with the giving plugin type and name.
-	GetBuilder(pt PluginType, name string) Builder
+	GetBuilder(pt PluginType, name string) (Builder, bool)
 
 	// DeleteBuilder deletes a builder with the giving plugin type and name.
 	DeleteBuilder(pt PluginType, name string)
 
 	// AddPlugin adds a plugin into this manager.
-	AddPlugin(p Plugin)
+	AddPlugin(p Plugin) error
 
 	// GetPlugin returns a plugin with the giving plugin type and name.
-	GetPlugin(pt PluginType, name string) Plugin
+	GetPlugin(pt PluginType, name string) (Plugin, bool)
 
 	// DeletePlugin deletes a plugin with the giving plugin type and name.
 	DeletePlugin(pt PluginType, name string)
@@ -71,11 +72,11 @@ type Builder func(conf interface{}) (Plugin, error)
 // Repository stores data related to plugin.
 type Repository interface {
 	// Add adds a data to this repository.
-	Add(pt PluginType, name string, data interface{})
+	Add(pt PluginType, name string, data interface{}) error
 
 	// Get gets a data with the giving type and name from this
 	// repository.
-	Get(pt PluginType, name string) interface{}
+	Get(pt PluginType, name string) (interface{}, bool)
 
 	// Delete deletes a data with the giving type and name from
 	// this repository.
@@ -92,44 +93,38 @@ type managerIml struct {
 
 var _ Manager = (*managerIml)(nil)
 
-func (m *managerIml) AddBuilder(pt PluginType, name string, b Builder) {
+func (m *managerIml) AddBuilder(pt PluginType, name string, b Builder) error {
 	if b == nil {
-		return
+		return fmt.Errorf("builder is nil")
 	}
-	m.builders.Add(pt, name, b)
+	return m.builders.Add(pt, name, b)
 }
 
-func (m *managerIml) GetBuilder(pt PluginType, name string) Builder {
-	data := m.builders.Get(pt, name)
-	if data == nil {
-		return nil
-	}
-	if builder, ok := data.(Builder); ok {
-		return builder
+func (m *managerIml) GetBuilder(pt PluginType, name string) (Builder, bool) {
+	data, ok := m.builders.Get(pt, name)
+	if ok {
+		return data.(Builder), true
 	}
-	return nil
+	return nil, false
 }
 
 func (m *managerIml) DeleteBuilder(pt PluginType, name string) {
 	m.builders.Delete(pt, name)
 }
 
-func (m *managerIml) AddPlugin(p Plugin) {
+func (m *managerIml) AddPlugin(p Plugin) error {
 	if p == nil {
-		return
+		return fmt.Errorf("plugin is nil")
 	}
-	m.plugins.Add(p.Type(), p.Name(), p)
+	return m.plugins.Add(p.Type(), p.Name(), p)
 }
 
-func (m *managerIml) GetPlugin(pt PluginType, name string) Plugin {
-	data := m.plugins.Get(pt, name)
-	if data == nil {
-		return nil
-	}
-	if plugin, ok := data.(Plugin); ok {
-		return plugin
+func (m *managerIml) GetPlugin(pt PluginType, name string) (Plugin, bool) {
+	data, ok := m.plugins.Get(pt, name)
+	if !ok {
+		return nil, false
 	}
-	return nil
+	return data.(Plugin), true
 }
 
 func (m *managerIml) DeletePlugin(pt PluginType, name string) {
@@ -141,36 +136,32 @@ func (m *managerIml) DeletePlugin(pt PluginType, name string) {
 
 type repositoryIml struct {
 	repos map[PluginType]*sync.Map
-	lock  sync.Mutex
+	sync.Mutex
 }
 
 var _ Repository = (*repositoryIml)(nil)
 
-func (r *repositoryIml) Add(pt PluginType, name string, data interface{}) {
-	if data == nil || !validate(pt, name) {
-		return
+func (r *repositoryIml) Add(pt PluginType, name string, data interface{}) error {
+	if data == nil {
+		return fmt.Errorf("data is nil")
+	}
+	if !validate(pt, name) {
+		return fmt.Errorf("invalid pluginType %s, name %s", pt, name)
 	}
-
 	m := r.getRepo(pt)
 	m.Store(name, data)
+	return nil
 }
 
-func (r *repositoryIml) Get(pt PluginType, name string) interface{} {
-	if !validate(pt, name) {
-		return nil
-	}
-
+func (r *repositoryIml) Get(pt PluginType, name string) (interface{}, bool) {
 	m := r.getRepo(pt)
 	if v, ok := m.Load(name); ok && v != nil {
-		return v
+		return v, true
 	}
-	return nil
+	return nil, false
 }
 
 func (r *repositoryIml) Delete(pt PluginType, name string) {
-	if !validate(pt, name) {
-		return
-	}
 	m := r.getRepo(pt)
 	m.Delete(name)
 }
@@ -184,17 +175,18 @@ func (r *repositoryIml) getRepo(pt PluginType) *sync.Map {
 		return m
 	}
 
-	r.lock.Lock()
+	r.Lock()
 	if m, ok = r.repos[pt]; !ok || m == nil {
 		m = &sync.Map{}
 		r.repos[pt] = m
 	}
-	r.lock.Unlock()
+	r.Unlock()
 	return m
 }
 
-// -----------------------------------------------------------------------------
-// helper functions
+/*
+   helper functions
+*/
 
 func validate(pt PluginType, name string) bool {
 	if name == "" {
diff --git a/cdnsystem/plugins/plugins.go b/cdnsystem/plugins/plugins.go
index 287b3988..e7b49194 100644
--- a/cdnsystem/plugins/plugins.go
+++ b/cdnsystem/plugins/plugins.go
@@ -24,32 +24,26 @@ import (
 
 var mgr = NewManager()
 
-// SetManager sets a Manager implementation instead of the default one.
-func SetManager(m Manager) {
-	mgr = m
-}
-
 // Initialize builds all plugins defined in config file.
 func Initialize(plugins map[PluginType][]*PluginProperties) error {
-	// todo Plugin loads sequence dependencies
 	for _, pt := range PluginTypes {
 		for _, v := range plugins[pt] {
 			if !v.Enable {
 				logger.Infof("plugin[%s][%s] is disabled", pt, v.Name)
 				continue
 			}
-			builder := mgr.GetBuilder(pt, v.Name)
-			if builder == nil {
-				return fmt.Errorf("cannot find builder to create plugin[%s][%s]",
-					pt, v.Name)
+			builder, ok := mgr.GetBuilder(pt, v.Name)
+			if !ok {
+				return fmt.Errorf("can not find builder to create plugin[%s][%s]", pt, v.Name)
 			}
 			p, err := builder(v.Config)
 			if err != nil {
-				return fmt.Errorf("failed to build plugin[%s][%s]: %v",
-					pt, v.Name, err)
+				return fmt.Errorf("failed to build plugin[%s][%s]: %v", pt, v.Name, err)
+			}
+			if err := mgr.AddPlugin(p); err != nil {
+				return fmt.Errorf("failed to add plugin[%s][%s]: %v", pt, v.Name, err)
 			}
-			mgr.AddPlugin(p)
-			logger.Infof("add plugin[%s][%s]", pt, v.Name)
+			logger.Infof("add plugin[%s][%s] success", pt, v.Name)
 		}
 	}
 	return nil
@@ -57,11 +51,11 @@ func Initialize(plugins map[PluginType][]*PluginProperties) error {
 
 // RegisterPluginBuilder register a plugin builder that will be called to create a new
 // plugin instant when cdn starts.
-func RegisterPluginBuilder(pt PluginType, name string, builder Builder) {
-	mgr.AddBuilder(pt, name, builder)
+func RegisterPluginBuilder(pt PluginType, name string, builder Builder) error {
+	return mgr.AddBuilder(pt, name, builder)
 }
 
 // GetPlugin returns a plugin instant with the giving plugin type and name.
-func GetPlugin(pt PluginType, name string) Plugin {
+func GetPlugin(pt PluginType, name string) (Plugin, bool) {
 	return mgr.GetPlugin(pt, name)
 }
diff --git a/cdnsystem/server/server.go b/cdnsystem/server/server.go
index 5e5b533b..330126b5 100644
--- a/cdnsystem/server/server.go
+++ b/cdnsystem/server/server.go
@@ -14,34 +14,25 @@
  * limitations under the License.
  */
 
+// cdn server
 package server
 
 import (
 	"context"
 	"fmt"
+	"runtime"
 
 	"d7y.io/dragonfly/v2/cdnsystem/config"
-	"d7y.io/dragonfly/v2/cdnsystem/daemon/mgr"
-	"d7y.io/dragonfly/v2/cdnsystem/daemon/mgr/cdn"
-	"d7y.io/dragonfly/v2/cdnsystem/daemon/mgr/cdn/storage"
-	"d7y.io/dragonfly/v2/cdnsystem/daemon/mgr/gc"
-	"d7y.io/dragonfly/v2/cdnsystem/daemon/mgr/progress"
-	"d7y.io/dragonfly/v2/cdnsystem/daemon/mgr/task"
+	"d7y.io/dragonfly/v2/cdnsystem/daemon/cdn"
+	"d7y.io/dragonfly/v2/cdnsystem/daemon/cdn/storage"
+	"d7y.io/dragonfly/v2/cdnsystem/daemon/gc"
+	"d7y.io/dragonfly/v2/cdnsystem/daemon/progress"
+	"d7y.io/dragonfly/v2/cdnsystem/daemon/task"
 	"d7y.io/dragonfly/v2/cdnsystem/plugins"
 	"d7y.io/dragonfly/v2/cdnsystem/server/service"
-	"d7y.io/dragonfly/v2/cdnsystem/source"
-
-	// Init http client
-	_ "d7y.io/dragonfly/v2/cdnsystem/source/httpprotocol"
-
-	// Init OSS client
-	_ "d7y.io/dragonfly/v2/cdnsystem/source/ossprotocol"
 	"d7y.io/dragonfly/v2/pkg/basic/dfnet"
 	"d7y.io/dragonfly/v2/pkg/rpc"
 	"d7y.io/dragonfly/v2/pkg/rpc/cdnsystem/server"
-
-	// Server registered to grpc
-	_ "d7y.io/dragonfly/v2/pkg/rpc/cdnsystem/server"
 	"d7y.io/dragonfly/v2/pkg/rpc/manager"
 	configServer "d7y.io/dragonfly/v2/pkg/rpc/manager/client"
 	"d7y.io/dragonfly/v2/pkg/util/net/iputils"
@@ -51,22 +42,19 @@ import (
 
 type Server struct {
 	Config       *config.Config
-	GCMgr        mgr.GCMgr
 	seedServer   server.SeederServer
 	configServer configServer.ManagerClient
 }
 
 // New creates a brand new server instance.
 func New(cfg *config.Config) (*Server, error) {
+	if ok := storage.IsSupport(cfg.StorageMode); !ok {
+		return nil, fmt.Errorf("os %s is not support storage mode %s", runtime.GOOS, cfg.StorageMode)
+	}
 	if err := plugins.Initialize(cfg.Plugins); err != nil {
 		return nil, err
 	}
 
-	// source client
-	sourceClient, err := source.NewSourceClient()
-	if err != nil {
-		return nil, errors.Wrapf(err, "failed to create source client")
-	}
 	// progress manager
 	progressMgr, err := progress.NewManager()
 	if err != nil {
@@ -74,23 +62,22 @@ func New(cfg *config.Config) (*Server, error) {
 	}
 
 	// storage manager
-	storageMgr, err := storage.Get(cfg.StorageMode)
-	if err != nil {
-		return nil, errors.Wrapf(err, "failed to create storage manager")
+	storageMgr, ok := storage.Get(cfg.StorageMode)
+	if !ok {
+		return nil, fmt.Errorf("can not find storage pattern %s", cfg.StorageMode)
 	}
 	// cdn manager
-	cdnMgr, err := cdn.NewManager(cfg, storageMgr, progressMgr, sourceClient)
+	cdnMgr, err := cdn.NewManager(cfg, storageMgr, progressMgr)
 	if err != nil {
 		return nil, errors.Wrapf(err, "failed to create cdn manager")
 	}
 	// task manager
-	taskMgr, err := task.NewManager(cfg, cdnMgr, progressMgr, sourceClient)
+	taskMgr, err := task.NewManager(cfg, cdnMgr, progressMgr)
 	if err != nil {
 		return nil, errors.Wrapf(err, "failed to create task manager")
 	}
 	storageMgr.Initialize(taskMgr)
 	// gc manager
-	gcMgr, err := gc.NewManager(taskMgr, cdnMgr)
 	if err != nil {
 		return nil, errors.Wrapf(err, "failed to create gc manager")
 	}
@@ -111,7 +98,6 @@ func New(cfg *config.Config) (*Server, error) {
 	}
 	return &Server{
 		Config:       cfg,
-		GCMgr:        gcMgr,
 		seedServer:   cdnSeedServer,
 		configServer: cfgServer,
 	}, nil
@@ -127,7 +113,7 @@ func (s *Server) Serve() (err error) {
 	ctx, cancel := context.WithCancel(context.Background())
 	defer cancel()
 	// start gc
-	err = s.GCMgr.StartGC(ctx)
+	err = gc.StartGC(ctx)
 	if err != nil {
 		return err
 	}
diff --git a/cdnsystem/server/service/cdn_seed_server.go b/cdnsystem/server/service/cdn_seed_server.go
index 9ccceee2..a80e8cad 100644
--- a/cdnsystem/server/service/cdn_seed_server.go
+++ b/cdnsystem/server/service/cdn_seed_server.go
@@ -21,10 +21,10 @@ import (
 	"fmt"
 	"strings"
 
-	"d7y.io/dragonfly/v2/cdnsystem/cdnerrors"
 	"d7y.io/dragonfly/v2/cdnsystem/cdnutil"
 	"d7y.io/dragonfly/v2/cdnsystem/config"
-	"d7y.io/dragonfly/v2/cdnsystem/daemon/mgr"
+	"d7y.io/dragonfly/v2/cdnsystem/daemon"
+	cdnerrors "d7y.io/dragonfly/v2/cdnsystem/errors"
 	"d7y.io/dragonfly/v2/cdnsystem/types"
 	"d7y.io/dragonfly/v2/pkg/dfcodes"
 	"d7y.io/dragonfly/v2/pkg/dferrors"
@@ -39,12 +39,12 @@ import (
 
 // CdnSeedServer is used to implement cdnsystem.SeederServer.
 type CdnSeedServer struct {
-	taskMgr mgr.SeedTaskMgr
+	taskMgr daemon.SeedTaskMgr
 	cfg     *config.Config
 }
 
 // NewManager returns a new Manager Object.
-func NewCdnSeedServer(cfg *config.Config, taskMgr mgr.SeedTaskMgr) (*CdnSeedServer, error) {
+func NewCdnSeedServer(cfg *config.Config, taskMgr daemon.SeedTaskMgr) (*CdnSeedServer, error) {
 	return &CdnSeedServer{
 		taskMgr: taskMgr,
 		cfg:     cfg,
diff --git a/cdnsystem/source/httpprotocol/http_source_client.go b/cdnsystem/source/httpprotocol/http_source_client.go
deleted file mode 100644
index f6b850ce..00000000
--- a/cdnsystem/source/httpprotocol/http_source_client.go
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- *     Copyright 2020 The Dragonfly Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package httpprotocol
-
-import (
-	"context"
-	"fmt"
-	"io"
-	"net"
-	"net/http"
-	"time"
-
-	"d7y.io/dragonfly/v2/cdnsystem/cdnerrors"
-	"d7y.io/dragonfly/v2/cdnsystem/source"
-	"d7y.io/dragonfly/v2/pkg/structure/maputils"
-	"d7y.io/dragonfly/v2/pkg/util/stringutils"
-	"d7y.io/dragonfly/v2/pkg/util/timeutils"
-	"github.com/go-http-utils/headers"
-	"github.com/pkg/errors"
-)
-
-const (
-	HTTPClient  = "http"
-	HTTPSClient = "https"
-)
-
-func init() {
-	httpSourceClient := NewHTTPSourceClient()
-	source.Register(HTTPClient, httpSourceClient)
-	source.Register(HTTPSClient, httpSourceClient)
-}
-
-// NewHTTPSourceClient returns a new HttpSourceClient.
-func NewHTTPSourceClient() source.ResourceClient {
-	transport := &http.Transport{
-		Proxy: http.ProxyFromEnvironment,
-		DialContext: (&net.Dialer{
-			Timeout:   3 * time.Second,
-			KeepAlive: 30 * time.Second,
-		}).DialContext,
-		MaxIdleConns:          100,
-		IdleConnTimeout:       90 * time.Second,
-		TLSHandshakeTimeout:   10 * time.Second,
-		ExpectContinueTimeout: 1 * time.Second,
-	}
-	return &httpSourceClient{
-		httpClient: &http.Client{
-			Transport: transport,
-		},
-	}
-}
-
-// httpSourceClient is an implementation of the interface of SourceClient.
-type httpSourceClient struct {
-	httpClient *http.Client
-}
-
-// GetContentLength get length of source
-// return -l if request fail
-// return -1 if response status is not StatusOK and StatusPartialContent
-func (client *httpSourceClient) GetContentLength(url string, header map[string]string) (int64, error) {
-	resp, err := client.requestWithHeader(http.MethodGet, url, header, 4*time.Second)
-	if err != nil {
-		return -1, errors.Wrapf(cdnerrors.ErrURLNotReachable, "get http header meta data failed:%v", err)
-	}
-	resp.Body.Close()
-	// todo 待讨论，这里如果是其他状态码是否要加入到 ErrURLNotReachable 中,如果不加入会下载404/频繁下载403
-	if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusPartialContent {
-		// todo 这种情况是否要和 err的情况作区分，类似于提出一种其他的错误类型用于表示这种错误是可以与url进行交互，但是状态码不符合预期
-		return -1, errors.Wrapf(cdnerrors.ErrURLNotReachable, "get http file length failed, unexpected code: %d", resp.StatusCode)
-	}
-	return resp.ContentLength, nil
-}
-
-// IsSupportRange checks if the source url support partial requests.
-func (client *httpSourceClient) IsSupportRange(url string, header map[string]string) (bool, error) {
-	// set header: header is a reference to map, should not change it
-	copied := maputils.DeepCopyMap(nil, header)
-	copied[headers.Range] = "bytes=0-0"
-
-	// send request
-	resp, err := client.requestWithHeader(http.MethodGet, url, copied, 4*time.Second)
-	if err != nil {
-		return false, err
-	}
-	resp.Body.Close()
-	return resp.StatusCode == http.StatusPartialContent, nil
-}
-
-// todo 考虑 expire，类似访问baidu网页是没有last-modified的
-// IsExpired checks if a resource received or stored is the same.
-func (client *httpSourceClient) IsExpired(url string, header, expireInfo map[string]string) (bool, error) {
-	lastModified := timeutils.UnixMillis(expireInfo[headers.LastModified])
-
-	eTag := expireInfo[headers.ETag]
-	if lastModified <= 0 && stringutils.IsBlank(eTag) {
-		return true, nil
-	}
-
-	// set header: header is a reference to map, should not change it
-	copied := maputils.DeepCopyMap(nil, header)
-	if lastModified > 0 {
-		copied[headers.IfModifiedSince] = expireInfo[headers.LastModified]
-	}
-	if !stringutils.IsBlank(eTag) {
-		copied[headers.IfNoneMatch] = eTag
-	}
-
-	// send request
-	resp, err := client.requestWithHeader(http.MethodGet, url, copied, 4*time.Second)
-	if err != nil {
-		// 如果获取失败，则认为没有过期，防止打爆源
-		return false, err
-	}
-	resp.Body.Close()
-	return resp.StatusCode != http.StatusNotModified, nil
-}
-
-// Download downloads the file from the original address
-func (client *httpSourceClient) Download(url string, header map[string]string) (io.ReadCloser, map[string]string, error) {
-	resp, err := client.requestWithHeader(http.MethodGet, url, header, 0)
-	if err != nil {
-		return nil, nil, err
-	}
-	if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusPartialContent {
-		expireInfo := map[string]string{
-			headers.LastModified: resp.Header.Get(headers.LastModified),
-			headers.ETag:         resp.Header.Get(headers.ETag),
-		}
-		return resp.Body, expireInfo, nil
-	}
-	resp.Body.Close()
-	return nil, nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
-}
-
-func (client *httpSourceClient) requestWithHeader(method string, url string, header map[string]string, timeout time.Duration) (*http.Response, error) {
-	req, err := http.NewRequest(method, url, nil)
-	if err != nil {
-		return nil, err
-	}
-	for k, v := range header {
-		req.Header.Add(k, v)
-	}
-	if timeout > 0 {
-		ctx, cancel := context.WithTimeout(context.Background(), timeout)
-		defer cancel()
-		req = req.WithContext(ctx)
-
-	}
-	return client.httpClient.Do(req)
-}
diff --git a/cdnsystem/source/source_client.go b/cdnsystem/source/source_client.go
deleted file mode 100644
index df9b7241..00000000
--- a/cdnsystem/source/source_client.go
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- *     Copyright 2020 The Dragonfly Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package source
-
-import (
-	"io"
-)
-
-var clients = make(map[string]ResourceClient)
-
-func Register(schema string, resourceClient ResourceClient) {
-	clients[schema] = resourceClient
-}
-
-func NewSourceClient() (ResourceClient, error) {
-	return &ResourceClientAdaptor{
-		clients: clients,
-	}, nil
-}
-
-// SourceClient supply apis that interact with the source.
-type ResourceClient interface {
-
-	// GetContentLength get content length from source
-	GetContentLength(url string, headers map[string]string) (int64, error)
-
-	// IsSupportRange checks if source supports breakpoint continuation
-	IsSupportRange(url string, headers map[string]string) (bool, error)
-
-	// IsExpired checks if cache is expired
-	IsExpired(url string, headers, expireInfo map[string]string) (bool, error)
-
-	// Download download from source
-	Download(url string, headers map[string]string) (io.ReadCloser, map[string]string, error)
-}
-
-type ResourceClientAdaptor struct {
-	clients map[string]ResourceClient
-}
-
-func (s *ResourceClientAdaptor) GetContentLength(url string, headers map[string]string) (int64, error) {
-	sourceClient, err := s.getSourceClient(url)
-	if err != nil {
-		return -1, err
-	}
-	return sourceClient.GetContentLength(url, headers)
-}
-
-func (s *ResourceClientAdaptor) IsSupportRange(url string, headers map[string]string) (bool, error) {
-	sourceClient, err := s.getSourceClient(url)
-	if err != nil {
-		return false, err
-	}
-	return sourceClient.IsSupportRange(url, headers)
-}
-
-func (s *ResourceClientAdaptor) IsExpired(url string, headers, expireInfo map[string]string) (bool, error) {
-	sourceClient, err := s.getSourceClient(url)
-	if err != nil {
-		return false, err
-	}
-	return sourceClient.IsExpired(url, headers, expireInfo)
-}
-
-func (s *ResourceClientAdaptor) Download(url string, headers map[string]string) (io.ReadCloser, map[string]string, error) {
-	sourceClient, err := s.getSourceClient(url)
-	if err != nil {
-		return nil, nil, err
-	}
-	return sourceClient.Download(url, headers)
-}
diff --git a/cdnsystem/source/source_client_util.go b/cdnsystem/source/source_client_util.go
deleted file mode 100644
index 54b045a5..00000000
--- a/cdnsystem/source/source_client_util.go
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- *     Copyright 2020 The Dragonfly Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package source
-
-import (
-	"fmt"
-	"strings"
-
-	"d7y.io/dragonfly/v2/pkg/dferrors"
-	"d7y.io/dragonfly/v2/pkg/util/stringutils"
-)
-
-func (s *ResourceClientAdaptor) getSchema(url string) (string, error) {
-	if stringutils.IsBlank(url) {
-		return "", dferrors.ErrEmptyValue
-	}
-	parts := strings.Split(url, ":")
-	if len(parts) == 0 {
-		return "", fmt.Errorf("failed to get schema for url(%s)", url)
-	}
-	return parts[0], nil
-}
-
-// Get a source client from manager with specified schema.
-func (s *ResourceClientAdaptor) getSourceClient(url string) (ResourceClient, error) {
-	schema, err := s.getSchema(url)
-	if err != nil {
-		return nil, err
-	}
-	client, ok := s.clients[schema]
-	if !ok || client == nil {
-		return nil, fmt.Errorf("does not support schema(%s) client", schema)
-	}
-	return client, nil
-}
diff --git a/cdnsystem/storedriver/driver.go b/cdnsystem/storedriver/driver.go
index 038b3cd8..6df61ff5 100644
--- a/cdnsystem/storedriver/driver.go
+++ b/cdnsystem/storedriver/driver.go
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+//go:generate mockgen -destination ./mock_driver.go -package storedriver d7y.io/dragonfly/v2/cdnsystem/storedriver Driver
 
 package storedriver
 
@@ -22,7 +23,7 @@ import (
 	"path/filepath"
 	"time"
 
-	"d7y.io/dragonfly/v2/cdnsystem/cdnerrors"
+	cdnerrors "d7y.io/dragonfly/v2/cdnsystem/errors"
 	"d7y.io/dragonfly/v2/cdnsystem/plugins"
 	"d7y.io/dragonfly/v2/pkg/unit"
 	"d7y.io/dragonfly/v2/pkg/util/stringutils"
diff --git a/cdnsystem/storedriver/local/local_driver.go b/cdnsystem/storedriver/local/local_driver.go
index 14fb17c9..c2de0f52 100644
--- a/cdnsystem/storedriver/local/local_driver.go
+++ b/cdnsystem/storedriver/local/local_driver.go
@@ -23,7 +23,7 @@ import (
 	"os"
 	"path/filepath"
 
-	"d7y.io/dragonfly/v2/cdnsystem/cdnerrors"
+	cdnerrors "d7y.io/dragonfly/v2/cdnsystem/errors"
 	"d7y.io/dragonfly/v2/cdnsystem/storedriver"
 	logger "d7y.io/dragonfly/v2/pkg/dflog"
 	"d7y.io/dragonfly/v2/pkg/synclock"
diff --git a/cdnsystem/storedriver/mock_driver.go b/cdnsystem/storedriver/mock_driver.go
new file mode 100644
index 00000000..435f3386
--- /dev/null
+++ b/cdnsystem/storedriver/mock_driver.go
@@ -0,0 +1,253 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: d7y.io/dragonfly/v2/cdnsystem/storedriver (interfaces: Driver)
+
+// Package storedriver is a generated GoMock package.
+package storedriver
+
+import (
+	io "io"
+	reflect "reflect"
+
+	unit "d7y.io/dragonfly/v2/pkg/unit"
+	gomock "github.com/golang/mock/gomock"
+)
+
+// MockDriver is a mock of Driver interface.
+type MockDriver struct {
+	ctrl     *gomock.Controller
+	recorder *MockDriverMockRecorder
+}
+
+// MockDriverMockRecorder is the mock recorder for MockDriver.
+type MockDriverMockRecorder struct {
+	mock *MockDriver
+}
+
+// NewMockDriver creates a new mock instance.
+func NewMockDriver(ctrl *gomock.Controller) *MockDriver {
+	mock := &MockDriver{ctrl: ctrl}
+	mock.recorder = &MockDriverMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockDriver) EXPECT() *MockDriverMockRecorder {
+	return m.recorder
+}
+
+// CreateBaseDir mocks base method.
+func (m *MockDriver) CreateBaseDir() error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "CreateBaseDir")
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// CreateBaseDir indicates an expected call of CreateBaseDir.
+func (mr *MockDriverMockRecorder) CreateBaseDir() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateBaseDir", reflect.TypeOf((*MockDriver)(nil).CreateBaseDir))
+}
+
+// Exits mocks base method.
+func (m *MockDriver) Exits(arg0 *Raw) bool {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Exits", arg0)
+	ret0, _ := ret[0].(bool)
+	return ret0
+}
+
+// Exits indicates an expected call of Exits.
+func (mr *MockDriverMockRecorder) Exits(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Exits", reflect.TypeOf((*MockDriver)(nil).Exits), arg0)
+}
+
+// Get mocks base method.
+func (m *MockDriver) Get(arg0 *Raw) (io.ReadCloser, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Get", arg0)
+	ret0, _ := ret[0].(io.ReadCloser)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// Get indicates an expected call of Get.
+func (mr *MockDriverMockRecorder) Get(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockDriver)(nil).Get), arg0)
+}
+
+// GetAvailSpace mocks base method.
+func (m *MockDriver) GetAvailSpace() (unit.Bytes, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetAvailSpace")
+	ret0, _ := ret[0].(unit.Bytes)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetAvailSpace indicates an expected call of GetAvailSpace.
+func (mr *MockDriverMockRecorder) GetAvailSpace() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAvailSpace", reflect.TypeOf((*MockDriver)(nil).GetAvailSpace))
+}
+
+// GetBytes mocks base method.
+func (m *MockDriver) GetBytes(arg0 *Raw) ([]byte, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetBytes", arg0)
+	ret0, _ := ret[0].([]byte)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetBytes indicates an expected call of GetBytes.
+func (mr *MockDriverMockRecorder) GetBytes(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBytes", reflect.TypeOf((*MockDriver)(nil).GetBytes), arg0)
+}
+
+// GetHomePath mocks base method.
+func (m *MockDriver) GetHomePath() string {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetHomePath")
+	ret0, _ := ret[0].(string)
+	return ret0
+}
+
+// GetHomePath indicates an expected call of GetHomePath.
+func (mr *MockDriverMockRecorder) GetHomePath() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHomePath", reflect.TypeOf((*MockDriver)(nil).GetHomePath))
+}
+
+// GetPath mocks base method.
+func (m *MockDriver) GetPath(arg0 *Raw) string {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetPath", arg0)
+	ret0, _ := ret[0].(string)
+	return ret0
+}
+
+// GetPath indicates an expected call of GetPath.
+func (mr *MockDriverMockRecorder) GetPath(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPath", reflect.TypeOf((*MockDriver)(nil).GetPath), arg0)
+}
+
+// GetTotalAndFreeSpace mocks base method.
+func (m *MockDriver) GetTotalAndFreeSpace() (unit.Bytes, unit.Bytes, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetTotalAndFreeSpace")
+	ret0, _ := ret[0].(unit.Bytes)
+	ret1, _ := ret[1].(unit.Bytes)
+	ret2, _ := ret[2].(error)
+	return ret0, ret1, ret2
+}
+
+// GetTotalAndFreeSpace indicates an expected call of GetTotalAndFreeSpace.
+func (mr *MockDriverMockRecorder) GetTotalAndFreeSpace() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTotalAndFreeSpace", reflect.TypeOf((*MockDriver)(nil).GetTotalAndFreeSpace))
+}
+
+// GetTotalSpace mocks base method.
+func (m *MockDriver) GetTotalSpace() (unit.Bytes, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetTotalSpace")
+	ret0, _ := ret[0].(unit.Bytes)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetTotalSpace indicates an expected call of GetTotalSpace.
+func (mr *MockDriverMockRecorder) GetTotalSpace() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTotalSpace", reflect.TypeOf((*MockDriver)(nil).GetTotalSpace))
+}
+
+// MoveFile mocks base method.
+func (m *MockDriver) MoveFile(arg0, arg1 string) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "MoveFile", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// MoveFile indicates an expected call of MoveFile.
+func (mr *MockDriverMockRecorder) MoveFile(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MoveFile", reflect.TypeOf((*MockDriver)(nil).MoveFile), arg0, arg1)
+}
+
+// Put mocks base method.
+func (m *MockDriver) Put(arg0 *Raw, arg1 io.Reader) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Put", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// Put indicates an expected call of Put.
+func (mr *MockDriverMockRecorder) Put(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Put", reflect.TypeOf((*MockDriver)(nil).Put), arg0, arg1)
+}
+
+// PutBytes mocks base method.
+func (m *MockDriver) PutBytes(arg0 *Raw, arg1 []byte) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "PutBytes", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// PutBytes indicates an expected call of PutBytes.
+func (mr *MockDriverMockRecorder) PutBytes(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutBytes", reflect.TypeOf((*MockDriver)(nil).PutBytes), arg0, arg1)
+}
+
+// Remove mocks base method.
+func (m *MockDriver) Remove(arg0 *Raw) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Remove", arg0)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// Remove indicates an expected call of Remove.
+func (mr *MockDriverMockRecorder) Remove(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockDriver)(nil).Remove), arg0)
+}
+
+// Stat mocks base method.
+func (m *MockDriver) Stat(arg0 *Raw) (*StorageInfo, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Stat", arg0)
+	ret0, _ := ret[0].(*StorageInfo)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// Stat indicates an expected call of Stat.
+func (mr *MockDriverMockRecorder) Stat(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stat", reflect.TypeOf((*MockDriver)(nil).Stat), arg0)
+}
+
+// Walk mocks base method.
+func (m *MockDriver) Walk(arg0 *Raw) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Walk", arg0)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// Walk indicates an expected call of Walk.
+func (mr *MockDriverMockRecorder) Walk(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Walk", reflect.TypeOf((*MockDriver)(nil).Walk), arg0)
+}
diff --git a/cdnsystem/storedriver/store_mgr.go b/cdnsystem/storedriver/store_mgr.go
index 94848516..d968a133 100644
--- a/cdnsystem/storedriver/store_mgr.go
+++ b/cdnsystem/storedriver/store_mgr.go
@@ -31,7 +31,7 @@ type DriverBuilder func(cfg *Config) (Driver, error)
 
 // Register defines an interface to register a driver with specified name.
 // All drivers should call this function to register itself to the driverFactory.
-func Register(name string, builder DriverBuilder) {
+func Register(name string, builder DriverBuilder) error {
 	name = strings.ToLower(name)
 	// plugin builder
 	var f = func(conf interface{}) (plugins.Plugin, error) {
@@ -49,17 +49,14 @@ func Register(name string, builder DriverBuilder) {
 
 		return newDriverPlugin(name, builder, cfg)
 	}
-	plugins.RegisterPluginBuilder(plugins.StorageDriverPlugin, name, f)
+	return plugins.RegisterPluginBuilder(plugins.StorageDriverPlugin, name, f)
 }
 
 // Get a store from manager with specified name.
-func Get(name string) (Driver, error) {
-	v := plugins.GetPlugin(plugins.StorageDriverPlugin, strings.ToLower(name))
-	if v == nil {
-		return nil, fmt.Errorf("storage: %s not existed", name)
+func Get(name string) (Driver, bool) {
+	v, ok := plugins.GetPlugin(plugins.StorageDriverPlugin, strings.ToLower(name))
+	if !ok {
+		return nil, false
 	}
-	if plugin, ok := v.(*driverPlugin); ok {
-		return plugin.instance, nil
-	}
-	return nil, fmt.Errorf("get store driver %s error: unknown reason", name)
+	return v.(*driverPlugin).instance, true
 }
diff --git a/cdnsystem/storedriver/store_util.go b/cdnsystem/storedriver/store_util.go
index ac5fa739..918e8a76 100644
--- a/cdnsystem/storedriver/store_util.go
+++ b/cdnsystem/storedriver/store_util.go
@@ -17,7 +17,7 @@
 package storedriver
 
 import (
-	"d7y.io/dragonfly/v2/cdnsystem/cdnerrors"
+	cdnerrors "d7y.io/dragonfly/v2/cdnsystem/errors"
 	"github.com/pkg/errors"
 )
 
diff --git a/cdnsystem/types/seed_piece_info.go b/cdnsystem/types/seed_piece_info.go
index 71acb152..f146d18e 100644
--- a/cdnsystem/types/seed_piece_info.go
+++ b/cdnsystem/types/seed_piece_info.go
@@ -28,8 +28,6 @@ type SeedPiece struct {
 	PieceLen    int32
 }
 
-type ItemType int8
-
 type PieceFormat int8
 
 const (
diff --git a/client/daemon/peer/piece_manager.go b/client/daemon/peer/piece_manager.go
index a1200019..dc07d362 100644
--- a/client/daemon/peer/piece_manager.go
+++ b/client/daemon/peer/piece_manager.go
@@ -22,13 +22,11 @@ import (
 	"math"
 	"time"
 
+	"d7y.io/dragonfly/v2/pkg/source"
 	"golang.org/x/time/rate"
 
 	cdnconfig "d7y.io/dragonfly/v2/cdnsystem/config"
-	"d7y.io/dragonfly/v2/cdnsystem/source"
 
-	// Init http client
-	_ "d7y.io/dragonfly/v2/cdnsystem/source/httpprotocol"
 	"d7y.io/dragonfly/v2/client/clientutil"
 	"d7y.io/dragonfly/v2/client/config"
 	"d7y.io/dragonfly/v2/client/daemon/storage"
@@ -49,20 +47,14 @@ type pieceManager struct {
 	*rate.Limiter
 	storageManager   storage.TaskStorageDriver
 	pieceDownloader  PieceDownloader
-	resourceClient   source.ResourceClient
 	computePieceSize func(contentLength int64) int32
 
 	calculateDigest bool
 }
 
 func NewPieceManager(s storage.TaskStorageDriver, opts ...func(*pieceManager)) (PieceManager, error) {
-	resourceClient, err := source.NewSourceClient()
-	if err != nil {
-		return nil, err
-	}
 	pm := &pieceManager{
 		storageManager:   s,
-		resourceClient:   resourceClient,
 		computePieceSize: computePieceSize,
 		calculateDigest:  true,
 	}
@@ -305,7 +297,7 @@ func (pm *pieceManager) DownloadSource(ctx context.Context, pt Task, request *sc
 	}
 	log := pt.Log()
 	log.Infof("start to download from source")
-	contentLength, err := pm.resourceClient.GetContentLength(request.Url, request.UrlMata.Header)
+	contentLength, err := source.GetContentLength(ctx, request.Url, request.UrlMata.Header)
 	if err != nil {
 		log.Warnf("get content length error: %s for %s", err, request.Url)
 	}
@@ -326,7 +318,7 @@ func (pm *pieceManager) DownloadSource(ctx context.Context, pt Task, request *sc
 	}
 	log.Debugf("get content length: %d", contentLength)
 	// 1. download piece from source
-	body, _, err := pm.resourceClient.Download(request.Url, request.UrlMata.Header)
+	body, err := source.Download(ctx, request.Url, request.UrlMata.Header)
 	if err != nil {
 		return err
 	}
diff --git a/client/dfget/dfget.go b/client/dfget/dfget.go
index 3d194c51..20238016 100644
--- a/client/dfget/dfget.go
+++ b/client/dfget/dfget.go
@@ -25,7 +25,6 @@ import (
 	"strings"
 	"time"
 
-	"d7y.io/dragonfly/v2/cdnsystem/source"
 	"d7y.io/dragonfly/v2/client/clientutil/progressbar"
 	"d7y.io/dragonfly/v2/client/config"
 	"d7y.io/dragonfly/v2/pkg/basic"
@@ -33,6 +32,7 @@ import (
 	logger "d7y.io/dragonfly/v2/pkg/dflog"
 	"d7y.io/dragonfly/v2/pkg/rpc/base"
 	dfdaemongrpc "d7y.io/dragonfly/v2/pkg/rpc/dfdaemon"
+	"d7y.io/dragonfly/v2/pkg/source"
 
 	// Init daemon rpc client
 	_ "d7y.io/dragonfly/v2/pkg/rpc/dfdaemon/client"
@@ -134,20 +134,18 @@ func downloadFromSource(cfg *config.DfgetConfig, hdr map[string]string) (err err
 
 	fmt.Println("dfget download error, try to download from source")
 	var (
-		resourceClient source.ResourceClient
-		target         *os.File
-		response       io.ReadCloser
-		_              map[string]string
-		written        int64
+		target   *os.File
+		response io.ReadCloser
+		_        map[string]string
+		written  int64
 	)
 
-	resourceClient, err = source.NewSourceClient()
 	if err != nil {
 		logger.Errorf("init source client error: %s", err)
 		return err
 	}
 
-	response, _, err = resourceClient.Download(cfg.URL, hdr)
+	response, err = source.Download(context.Background(), cfg.URL, hdr)
 	if err != nil {
 		logger.Errorf("download from source error: %s", err)
 		return err
diff --git a/cmd/cdn/main.go b/cmd/cdn/main.go
index 4554e8b0..0012a972 100644
--- a/cmd/cdn/main.go
+++ b/cmd/cdn/main.go
@@ -16,7 +16,14 @@
 
 package main
 
-import "d7y.io/dragonfly/v2/cmd/cdn/cmd"
+import (
+	"d7y.io/dragonfly/v2/cmd/cdn/cmd"
+
+	// Init http client
+	_ "d7y.io/dragonfly/v2/pkg/source/httpprotocol"
+	// Init OSS client
+	_ "d7y.io/dragonfly/v2/pkg/source/ossprotocol"
+)
 
 func main() {
 	cmd.Execute()
diff --git a/cmd/dfget/main.go b/cmd/dfget/main.go
index ed187113..7b96372d 100644
--- a/cmd/dfget/main.go
+++ b/cmd/dfget/main.go
@@ -16,7 +16,13 @@
 
 package main
 
-import "d7y.io/dragonfly/v2/cmd/dfget/cmd"
+import (
+	"d7y.io/dragonfly/v2/cmd/dfget/cmd"
+	// Init http client
+	_ "d7y.io/dragonfly/v2/pkg/source/httpprotocol"
+	// Init OSS client
+	_ "d7y.io/dragonfly/v2/pkg/source/ossprotocol"
+)
 
 func main() {
 	cmd.Execute()
diff --git a/go.mod b/go.mod
index 23751e7f..c8c7e578 100644
--- a/go.mod
+++ b/go.mod
@@ -4,6 +4,7 @@ go 1.15
 
 require (
 	github.com/HuKeping/rbtree v0.0.0-20210106022122-8ad34838eb2b
+	github.com/agiledragon/gomonkey v2.0.2+incompatible // indirect
 	github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
 	github.com/alicebob/miniredis/v2 v2.14.5
 	github.com/aliyun/aliyun-oss-go-sdk v2.1.6+incompatible
@@ -15,7 +16,7 @@ require (
 	github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a
 	github.com/go-openapi/spec v0.20.3 // indirect
 	github.com/go-redis/cache/v8 v8.4.1
-	github.com/go-redis/redis/v8 v8.9.0
+	github.com/go-redis/redis/v8 v8.10.0
 	github.com/gofrs/flock v0.8.0
 	github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e
 	github.com/golang/mock v1.5.0
@@ -23,6 +24,7 @@ require (
 	github.com/google/uuid v1.1.5
 	github.com/gorilla/mux v1.7.3
 	github.com/iancoleman/strcase v0.1.3
+	github.com/jarcoal/httpmock v1.0.8
 	github.com/mailru/easyjson v0.7.7 // indirect
 	github.com/mattn/go-runewidth v0.0.9
 	github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db
@@ -56,7 +58,7 @@ require (
 	golang.org/x/time v0.0.0-20201208040808-7e3f01d25324
 	google.golang.org/grpc v1.36.0
 	google.golang.org/protobuf v1.25.0
-	gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
+	gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
 	gopkg.in/errgo.v2 v2.1.0
 	gopkg.in/natefinch/lumberjack.v2 v2.0.0
 	gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
diff --git a/go.sum b/go.sum
index 6ce1e37e..2d66d24e 100644
--- a/go.sum
+++ b/go.sum
@@ -26,6 +26,8 @@ github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tN
 github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
 github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
 github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
+github.com/agiledragon/gomonkey v2.0.2+incompatible h1:eXKi9/piiC3cjJD1658mEE2o3NjkJ5vDLgYjCQu0Xlw=
+github.com/agiledragon/gomonkey v2.0.2+incompatible/go.mod h1:2NGfXu1a80LLr2cmWXGBDaHEjb1idR6+FVlX5T3D9hw=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
@@ -149,8 +151,8 @@ github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GO
 github.com/go-redis/cache/v8 v8.4.1 h1:jq0fw7hcUJzsoS9qPYjXzcAT/rm19TRMB1vEMG3m950=
 github.com/go-redis/cache/v8 v8.4.1/go.mod h1:iyYQNUxMsz6cPfTX3h4sT4lUmDXV0mDuEyeAn2o1btI=
 github.com/go-redis/redis/v8 v8.4.4/go.mod h1:nA0bQuF0i5JFx4Ta9RZxGKXFrQ8cRWntra97f0196iY=
-github.com/go-redis/redis/v8 v8.9.0 h1:FTTbB7WqlXfVNdVv0SsxA+oVi0bAwit6bMe3IUucq2o=
-github.com/go-redis/redis/v8 v8.9.0/go.mod h1:ik7vb7+gm8Izylxu6kf6wG26/t2VljgCfSQ1DM4O1uU=
+github.com/go-redis/redis/v8 v8.10.0 h1:OZwrQKuZqdJ4QIM8wn8rnuz868Li91xA3J2DEq+TPGA=
+github.com/go-redis/redis/v8 v8.10.0/go.mod h1:vXLTvigok0VtUX0znvbcEW1SOt4OA9CU1ZfnOtKOaiM=
 github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
 github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
@@ -242,6 +244,8 @@ github.com/iancoleman/strcase v0.1.3 h1:dJBk1m2/qjL1twPLf68JND55vvivMupZ4wIzE8CT
 github.com/iancoleman/strcase v0.1.3/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE=
 github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/jarcoal/httpmock v1.0.8 h1:8kI16SoO6LQKgPE7PvQuV+YuD/inwHd7fOOe2zMbo4k=
+github.com/jarcoal/httpmock v1.0.8/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
 github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
 github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
 github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E=
@@ -509,6 +513,7 @@ golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKG
 golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
 golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449 h1:xUIPaMhvROX9dhPvRCenIJtU78+lbEenGbgqB5hfHCQ=
 golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@@ -624,6 +629,7 @@ golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtn
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20201120155355-20be4ac4bd6e h1:t96dS3DO8DGjawSLJL/HIdz8CycAd2v07XxqB3UPTi0=
 golang.org/x/tools v0.0.0-20201120155355-20be4ac4bd6e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e h1:4nW4NLDYnU28ojHaHO8OVxFHk/aQ33U01a9cjED+pzE=
 golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
diff --git a/manager/apis/v2/main.go b/manager/apis/v2/main.go
index 1ad32fcf..2033ace8 100644
--- a/manager/apis/v2/main.go
+++ b/manager/apis/v2/main.go
@@ -1,9 +1,12 @@
 package main
 
 import (
+	"os"
+
 	"d7y.io/dragonfly/v2/manager/apis/v2/handler"
 	"d7y.io/dragonfly/v2/manager/config"
 	"d7y.io/dragonfly/v2/manager/server/service"
+	logger "d7y.io/dragonfly/v2/pkg/dflog"
 	"github.com/gin-gonic/gin"
 	swaggerFiles "github.com/swaggo/files"
 	ginSwagger "github.com/swaggo/gin-swagger"
@@ -27,7 +30,12 @@ import (
 // @host localhost:8080
 // @BasePath /api/v2
 func main() {
-	server := service.NewManagerServer(config.New())
+	server, err := service.NewManagerServer(config.New())
+	if err != nil {
+		logger.Error(err)
+		os.Exit(1)
+	}
+
 	router := gin.New()
 	handler := handler.NewHandler(server)
 
diff --git a/manager/server/server.go b/manager/server/server.go
index e2f3d1bd..d552a807 100644
--- a/manager/server/server.go
+++ b/manager/server/server.go
@@ -18,6 +18,7 @@ package server
 
 import (
 	"context"
+	"fmt"
 	"net/http"
 	"os"
 	"os/signal"
@@ -31,7 +32,6 @@ import (
 
 	// manager server rpc
 	_ "d7y.io/dragonfly/v2/pkg/rpc/manager/server"
-	"github.com/pkg/errors"
 )
 
 type Server struct {
@@ -42,24 +42,25 @@ type Server struct {
 }
 
 func New(cfg *config.Config) (*Server, error) {
-	if ms := service.NewManagerServer(cfg); ms != nil {
-		router, err := InitRouter(ms)
-		if err != nil {
-			return nil, err
-		}
-
-		return &Server{
-			cfg: cfg,
-			ms:  ms,
-			httpServer: &http.Server{
-				Addr:    ":8080",
-				Handler: router,
-			},
-			stop: make(chan struct{}),
-		}, nil
+	ms, err := service.NewManagerServer(cfg)
+	if err != nil {
+		return nil, fmt.Errorf("Failed to create manager server: %s", err)
 	}
+	router, err := InitRouter(ms)
+	if err != nil {
+		return nil, err
+	}
+
+	return &Server{
+		cfg: cfg,
+		ms:  ms,
+		httpServer: &http.Server{
+			Addr:    ":8080",
+			Handler: router,
+		},
+		stop: make(chan struct{}),
+	}, nil
 
-	return nil, errors.New("failed to create manager server")
 }
 
 func (s *Server) Serve() error {
diff --git a/manager/server/service/manager_server.go b/manager/server/service/manager_server.go
index 7b1e5a92..32fab60e 100644
--- a/manager/server/service/manager_server.go
+++ b/manager/server/service/manager_server.go
@@ -28,7 +28,7 @@ type ManagerServer struct {
 	redisClient *dc.RedisClient
 }
 
-func NewManagerServer(cfg *config.Config) *ManagerServer {
+func NewManagerServer(cfg *config.Config) (*ManagerServer, error) {
 	var err error
 	mgr := &ManagerServer{}
 	defer func() {
@@ -38,37 +38,37 @@ func NewManagerServer(cfg *config.Config) *ManagerServer {
 	}()
 
 	if err = cfg.Valid(); err != nil {
-		return nil
+		return mgr, err
 	}
 
 	mgr.identifier = hostidentifier.NewIdentifier()
 
 	mgr.store, err = client.NewStore(cfg)
 	if err != nil {
-		return nil
+		return mgr, err
 	}
 
 	mgr.lessor, err = lease.NewLessor(mgr.store)
 	if err != nil {
-		return nil
+		return mgr, err
 	}
 
 	mgr.redisClient, err = dc.NewRedisClient(cfg.Redis)
 	if err != nil {
-		return nil
+		return mgr, err
 	}
 
 	mgr.configSvc, err = configsvc.NewConfigSvc(mgr.store, mgr.identifier, mgr.lessor, mgr.redisClient)
 	if err != nil {
-		return nil
+		return mgr, err
 	}
 
 	mgr.hostManager, err = host.NewManager(cfg.HostService)
 	if err != nil {
-		return nil
+		return mgr, err
 	}
 
-	return mgr
+	return mgr, nil
 }
 
 func (ms *ManagerServer) Close() error {
diff --git a/pkg/ratelimiter/ratelimiter/ratelimiter.go b/pkg/ratelimiter/ratelimiter/ratelimiter.go
index 91226960..e3120e42 100644
--- a/pkg/ratelimiter/ratelimiter/ratelimiter.go
+++ b/pkg/ratelimiter/ratelimiter/ratelimiter.go
@@ -114,13 +114,13 @@ func (rl *RateLimiter) computeRatePerWindow() {
 	if rl.rate <= 0 {
 		return
 	}
-	ratePerWindow := int64(rl.rate) * int64(rl.window) / 1000
+	ratePerWindow := rl.rate * rl.window / 1000
 	if ratePerWindow > 0 {
 		rl.ratePerWindow = ratePerWindow
 		return
 	}
 	rl.ratePerWindow = 1
-	rl.setWindow(int64(rl.ratePerWindow * 1000 / rl.rate))
+	rl.setWindow(rl.ratePerWindow * 1000 / rl.rate)
 }
 
 func (rl *RateLimiter) createTokens(timeNano int64) int64 {
diff --git a/pkg/rpc/cdnsystem/client/client.go b/pkg/rpc/cdnsystem/client/client.go
index dc95f571..8409805b 100644
--- a/pkg/rpc/cdnsystem/client/client.go
+++ b/pkg/rpc/cdnsystem/client/client.go
@@ -77,6 +77,8 @@ type cdnClient struct {
 	*rpc.Connection
 }
 
+var _ CdnClient = (*cdnClient)(nil)
+
 func (cc *cdnClient) getCdnClient(key string, stick bool) (cdnsystem.SeederClient, string, error) {
 	clientConn, err := cc.Connection.GetClientConn(key, stick)
 	if err != nil {
@@ -113,8 +115,3 @@ func (cc *cdnClient) GetPieceTasks(ctx context.Context, addr dfnet.NetAddr, req
 	}
 	return nil, err
 }
-
-func init() {
-	var cc *cdnClient = nil
-	var _ CdnClient = cc
-}
diff --git a/pkg/rpc/dfdaemon/client/client.go b/pkg/rpc/dfdaemon/client/client.go
index 75d80cd8..35ac6e66 100644
--- a/pkg/rpc/dfdaemon/client/client.go
+++ b/pkg/rpc/dfdaemon/client/client.go
@@ -32,6 +32,8 @@ import (
 	"google.golang.org/grpc"
 )
 
+var _ DaemonClient = (*daemonClient)(nil)
+
 func GetClientByAddr(addrs []dfnet.NetAddr, opts ...grpc.DialOption) (DaemonClient, error) {
 	if len(addrs) == 0 {
 		return nil, errors.New("address list of daemon is empty")
@@ -130,8 +132,3 @@ func (dc *daemonClient) CheckHealth(ctx context.Context, target dfnet.NetAddr, o
 
 	return
 }
-
-func init() {
-	var dc *daemonClient = nil
-	var _ DaemonClient = dc
-}
diff --git a/pkg/rpc/manager/client/client.go b/pkg/rpc/manager/client/client.go
index 0b6fc0c7..5dc13125 100644
--- a/pkg/rpc/manager/client/client.go
+++ b/pkg/rpc/manager/client/client.go
@@ -39,7 +39,6 @@ func NewClient(addrs []dfnet.NetAddr, opts ...grpc.DialOption) (ManagerClient, e
 		return nil, errors.New("manager address list is empty")
 	}
 	mc := &managerClient{
-		locker: &sync.RWMutex{},
 		Connection: rpc.NewConnection(context.Background(), "manager", addrs, []rpc.ConnOption{
 			rpc.WithConnExpireTime(10 * time.Second),
 			rpc.WithDialOption(opts),
@@ -78,17 +77,14 @@ type ManagerClient interface {
 }
 
 type managerClient struct {
-	locker *sync.RWMutex
+	locker sync.RWMutex
 	*rpc.Connection
 	curClient      manager.ManagerClient
 	curManagerNode string
 	adders         []dfnet.NetAddr
 }
 
-func init() {
-	var client *managerClient = nil
-	var _ ManagerClient = client
-}
+var _ ManagerClient = (*managerClient)(nil)
 
 func (mc *managerClient) replaceManager(cause error) error {
 	if e, ok := cause.(*dferrors.DfError); ok {
@@ -217,7 +213,4 @@ func (mc *managerClient) GetCdnClusterConfig(ctx context.Context, req *manager.G
 	return cc, nil
 }
 
-func init() {
-	var mc *managerClient = nil
-	var _ ManagerClient = mc
-}
+var _ ManagerClient = (*managerClient)(nil)
diff --git a/pkg/rpc/scheduler/client/client.go b/pkg/rpc/scheduler/client/client.go
index 6aa1d6b4..50f29b61 100644
--- a/pkg/rpc/scheduler/client/client.go
+++ b/pkg/rpc/scheduler/client/client.go
@@ -242,7 +242,4 @@ func (sc *schedulerClient) LeaveTask(ctx context.Context, pt *scheduler.PeerTarg
 	return
 }
 
-func init() {
-	var sc *schedulerClient = nil
-	var _ SchedulerClient = sc
-}
+var _ SchedulerClient = (*schedulerClient)(nil)
diff --git a/cdnsystem/source/hdfs/hdfs_source_client.go b/pkg/source/hdfs/hdfs_source_client.go
similarity index 100%
rename from cdnsystem/source/hdfs/hdfs_source_client.go
rename to pkg/source/hdfs/hdfs_source_client.go
diff --git a/pkg/source/httpprotocol/http_source_client.go b/pkg/source/httpprotocol/http_source_client.go
new file mode 100644
index 00000000..72c95a06
--- /dev/null
+++ b/pkg/source/httpprotocol/http_source_client.go
@@ -0,0 +1,189 @@
+/*
+ *     Copyright 2020 The Dragonfly Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package httpprotocol
+
+import (
+	"context"
+	"fmt"
+	"io"
+	"net"
+	"net/http"
+	"time"
+
+	cdnerrors "d7y.io/dragonfly/v2/cdnsystem/errors"
+	"d7y.io/dragonfly/v2/pkg/source"
+	"d7y.io/dragonfly/v2/pkg/structure/maputils"
+	"d7y.io/dragonfly/v2/pkg/util/stringutils"
+	"d7y.io/dragonfly/v2/pkg/util/timeutils"
+	"github.com/go-http-utils/headers"
+	"github.com/pkg/errors"
+)
+
+const (
+	HTTPClient  = "http"
+	HTTPSClient = "https"
+)
+
+var defaultHTTPClient *http.Client
+var _ source.ResourceClient = (*httpSourceClient)(nil)
+
+func init() {
+	transport := http.DefaultTransport.(*http.Transport).Clone()
+	transport.DialContext = (&net.Dialer{
+		Timeout:   3 * time.Second,
+		KeepAlive: 30 * time.Second,
+	}).DialContext
+	defaultHTTPClient = &http.Client{
+		Transport: transport,
+	}
+	httpSourceClient := NewHTTPSourceClient()
+	source.Register(HTTPClient, httpSourceClient)
+	source.Register(HTTPSClient, httpSourceClient)
+}
+
+// httpSourceClient is an implementation of the interface of source.ResourceClient.
+type httpSourceClient struct {
+	httpClient *http.Client
+}
+
+// NewHTTPSourceClient returns a new HTTPSourceClientOption.
+func NewHTTPSourceClient(opts ...HTTPSourceClientOption) source.ResourceClient {
+	client := &httpSourceClient{
+		httpClient: defaultHTTPClient,
+	}
+	for i := range opts {
+		opts[i](client)
+	}
+	return client
+}
+
+type HTTPSourceClientOption func(p *httpSourceClient)
+
+func WithHTTPClient(client *http.Client) HTTPSourceClientOption {
+	return func(sourceClient *httpSourceClient) {
+		sourceClient.httpClient = client
+	}
+}
+
+func (client *httpSourceClient) GetContentLength(ctx context.Context, url string, header source.Header) (int64, error) {
+	resp, err := client.doRequest(ctx, http.MethodGet, url, header)
+	if err != nil {
+		return -1, errors.Wrapf(cdnerrors.ErrURLNotReachable, "get http header meta data failed: %v", err)
+	}
+	resp.Body.Close()
+	// todo Here if other status codes should be added to ErrURLNotReachable, if not, it will be downloaded frequently for 404 or 403
+	if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusPartialContent {
+		// todo Whether this situation should be distinguished from the err situation, similar to proposing another error type to indicate that this  error can interact with the URL, but the status code does not meet expectations
+		return -1, errors.Wrapf(cdnerrors.ErrURLNotReachable, "failed to get http resource length, unexpected code: %d", resp.StatusCode)
+	}
+	return resp.ContentLength, nil
+}
+
+func (client *httpSourceClient) IsSupportRange(ctx context.Context, url string, header source.Header) (bool, error) {
+	copied := maputils.DeepCopyMap(nil, header)
+	copied[headers.Range] = "bytes=0-0"
+
+	resp, err := client.doRequest(ctx, http.MethodGet, url, copied)
+	if err != nil {
+		return false, err
+	}
+	resp.Body.Close()
+	return resp.StatusCode == http.StatusPartialContent, nil
+}
+
+// todo Consider the situation where there is no last-modified such as baidu
+func (client *httpSourceClient) IsExpired(ctx context.Context, url string, header source.Header, expireInfo map[string]string) (bool, error) {
+	lastModified := timeutils.UnixMillis(expireInfo[headers.LastModified])
+
+	eTag := expireInfo[headers.ETag]
+	if lastModified <= 0 && stringutils.IsBlank(eTag) {
+		return true, nil
+	}
+
+	// set header: header is a reference to map, should not change it
+	copied := maputils.DeepCopyMap(nil, header)
+	if lastModified > 0 {
+		copied[headers.IfModifiedSince] = expireInfo[headers.LastModified]
+	}
+	if !stringutils.IsBlank(eTag) {
+		copied[headers.IfNoneMatch] = eTag
+	}
+
+	// send request
+	resp, err := client.doRequest(ctx, http.MethodGet, url, copied)
+	if err != nil {
+		// If it fails to get the result, it is considered that the source has not expired, to prevent the source from exploding
+		return false, err
+	}
+	resp.Body.Close()
+	return resp.StatusCode != http.StatusNotModified, nil
+}
+
+func (client *httpSourceClient) Download(ctx context.Context, url string, header source.Header) (io.ReadCloser, error) {
+	resp, err := client.doRequest(ctx, http.MethodGet, url, header)
+	if err != nil {
+		return nil, err
+	}
+	if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusPartialContent {
+		return resp.Body, nil
+	}
+	resp.Body.Close()
+	return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
+}
+
+func (client *httpSourceClient) DownloadWithExpire(ctx context.Context, url string, header source.Header) (io.ReadCloser, map[string]string, error) {
+	resp, err := client.doRequest(ctx, http.MethodGet, url, header)
+	if err != nil {
+		return nil, nil, err
+	}
+	if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusPartialContent {
+		expireInfo := map[string]string{
+			headers.LastModified: resp.Header.Get(headers.LastModified),
+			headers.ETag:         resp.Header.Get(headers.ETag),
+		}
+		return resp.Body, expireInfo, nil
+	}
+	resp.Body.Close()
+	return nil, nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
+}
+
+func (client *httpSourceClient) GetExpireInfo(ctx context.Context, url string, header source.Header) (map[string]string, error) {
+	resp, err := client.doRequest(ctx, http.MethodGet, url, header)
+	if err != nil {
+		return nil, err
+	}
+	resp.Body.Close()
+	if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusPartialContent {
+		expireInfo := map[string]string{
+			headers.LastModified: resp.Header.Get(headers.LastModified),
+			headers.ETag:         resp.Header.Get(headers.ETag),
+		}
+		return expireInfo, nil
+	}
+	return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
+}
+
+func (client *httpSourceClient) doRequest(ctx context.Context, method string, url string, header source.Header) (*http.Response, error) {
+	req, err := http.NewRequestWithContext(ctx, method, url, nil)
+	if err != nil {
+		return nil, err
+	}
+	for k, v := range header {
+		req.Header.Add(k, v)
+	}
+	return client.httpClient.Do(req)
+}
diff --git a/pkg/source/mock/mock_source_client.go b/pkg/source/mock/mock_source_client.go
new file mode 100644
index 00000000..ad27c022
--- /dev/null
+++ b/pkg/source/mock/mock_source_client.go
@@ -0,0 +1,128 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: d7y.io/dragonfly/v2/pkg/source (interfaces: ResourceClient)
+
+// Package mock is a generated GoMock package.
+package mock
+
+import (
+	context "context"
+	io "io"
+	reflect "reflect"
+
+	source "d7y.io/dragonfly/v2/pkg/source"
+	gomock "github.com/golang/mock/gomock"
+)
+
+// MockResourceClient is a mock of ResourceClient interface.
+type MockResourceClient struct {
+	ctrl     *gomock.Controller
+	recorder *MockResourceClientMockRecorder
+}
+
+// MockResourceClientMockRecorder is the mock recorder for MockResourceClient.
+type MockResourceClientMockRecorder struct {
+	mock *MockResourceClient
+}
+
+// NewMockResourceClient creates a new mock instance.
+func NewMockResourceClient(ctrl *gomock.Controller) *MockResourceClient {
+	mock := &MockResourceClient{ctrl: ctrl}
+	mock.recorder = &MockResourceClientMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockResourceClient) EXPECT() *MockResourceClientMockRecorder {
+	return m.recorder
+}
+
+// Download mocks base method.
+func (m *MockResourceClient) Download(arg0 context.Context, arg1 string, arg2 source.Header) (io.ReadCloser, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Download", arg0, arg1, arg2)
+	ret0, _ := ret[0].(io.ReadCloser)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// Download indicates an expected call of Download.
+func (mr *MockResourceClientMockRecorder) Download(arg0, arg1, arg2 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Download", reflect.TypeOf((*MockResourceClient)(nil).Download), arg0, arg1, arg2)
+}
+
+// DownloadWithExpire mocks base method.
+func (m *MockResourceClient) DownloadWithExpire(arg0 context.Context, arg1 string, arg2 source.Header) (io.ReadCloser, map[string]string, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "DownloadWithExpire", arg0, arg1, arg2)
+	ret0, _ := ret[0].(io.ReadCloser)
+	ret1, _ := ret[1].(map[string]string)
+	ret2, _ := ret[2].(error)
+	return ret0, ret1, ret2
+}
+
+// DownloadWithExpire indicates an expected call of DownloadWithExpire.
+func (mr *MockResourceClientMockRecorder) DownloadWithExpire(arg0, arg1, arg2 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DownloadWithExpire", reflect.TypeOf((*MockResourceClient)(nil).DownloadWithExpire), arg0, arg1, arg2)
+}
+
+// GetContentLength mocks base method.
+func (m *MockResourceClient) GetContentLength(arg0 context.Context, arg1 string, arg2 source.Header) (int64, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetContentLength", arg0, arg1, arg2)
+	ret0, _ := ret[0].(int64)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetContentLength indicates an expected call of GetContentLength.
+func (mr *MockResourceClientMockRecorder) GetContentLength(arg0, arg1, arg2 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetContentLength", reflect.TypeOf((*MockResourceClient)(nil).GetContentLength), arg0, arg1, arg2)
+}
+
+// GetExpireInfo mocks base method.
+func (m *MockResourceClient) GetExpireInfo(arg0 context.Context, arg1 string, arg2 source.Header) (map[string]string, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetExpireInfo", arg0, arg1, arg2)
+	ret0, _ := ret[0].(map[string]string)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetExpireInfo indicates an expected call of GetExpireInfo.
+func (mr *MockResourceClientMockRecorder) GetExpireInfo(arg0, arg1, arg2 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetExpireInfo", reflect.TypeOf((*MockResourceClient)(nil).GetExpireInfo), arg0, arg1, arg2)
+}
+
+// IsExpired mocks base method.
+func (m *MockResourceClient) IsExpired(arg0 context.Context, arg1 string, arg2 source.Header, arg3 map[string]string) (bool, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "IsExpired", arg0, arg1, arg2, arg3)
+	ret0, _ := ret[0].(bool)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// IsExpired indicates an expected call of IsExpired.
+func (mr *MockResourceClientMockRecorder) IsExpired(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsExpired", reflect.TypeOf((*MockResourceClient)(nil).IsExpired), arg0, arg1, arg2, arg3)
+}
+
+// IsSupportRange mocks base method.
+func (m *MockResourceClient) IsSupportRange(arg0 context.Context, arg1 string, arg2 source.Header) (bool, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "IsSupportRange", arg0, arg1, arg2)
+	ret0, _ := ret[0].(bool)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// IsSupportRange indicates an expected call of IsSupportRange.
+func (mr *MockResourceClientMockRecorder) IsSupportRange(arg0, arg1, arg2 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsSupportRange", reflect.TypeOf((*MockResourceClient)(nil).IsSupportRange), arg0, arg1, arg2)
+}
diff --git a/cdnsystem/source/ossprotocol/oss_source_client.go b/pkg/source/ossprotocol/oss_source_client.go
similarity index 66%
rename from cdnsystem/source/ossprotocol/oss_source_client.go
rename to pkg/source/ossprotocol/oss_source_client.go
index 3addf71a..8a9b1572 100644
--- a/cdnsystem/source/ossprotocol/oss_source_client.go
+++ b/pkg/source/ossprotocol/oss_source_client.go
@@ -17,6 +17,7 @@
 package ossprotocol
 
 import (
+	"context"
 	"fmt"
 	"io"
 	"net/http"
@@ -24,8 +25,8 @@ import (
 	"strconv"
 	"sync"
 
-	"d7y.io/dragonfly/v2/cdnsystem/cdnerrors"
-	"d7y.io/dragonfly/v2/cdnsystem/source"
+	cdnerrors "d7y.io/dragonfly/v2/cdnsystem/errors"
+	"d7y.io/dragonfly/v2/pkg/source"
 	"d7y.io/dragonfly/v2/pkg/util/stringutils"
 	"github.com/aliyun/aliyun-oss-go-sdk/oss"
 	"github.com/go-http-utils/headers"
@@ -33,33 +34,50 @@ import (
 )
 
 const ossClient = "oss"
+
 const (
 	endpoint        = "endpoint"
 	accessKeyID     = "accessKeyID"
 	accessKeySecret = "accessKeySecret"
 )
 
+var _ source.ResourceClient = (*ossSourceClient)(nil)
+
 func init() {
 	sourceClient := NewOSSSourceClient()
 	source.Register(ossClient, sourceClient)
 }
 
-func NewOSSSourceClient() source.ResourceClient {
-	return &ossSourceClient{
+func NewOSSSourceClient(opts ...OssSourceClientOption) source.ResourceClient {
+	sourceClient := &ossSourceClient{
 		clientMap: sync.Map{},
 		accessMap: sync.Map{},
 	}
+	for i := range opts {
+		opts[i](sourceClient)
+	}
+	return sourceClient
 }
 
+type OssSourceClientOption func(p *ossSourceClient)
+
 // httpSourceClient is an implementation of the interface of SourceClient.
 type ossSourceClient struct {
-	// endpoint -> ossClient
+	// endpoint_accessKeyID_accessKeySecret -> ossClient
 	clientMap sync.Map
 	accessMap sync.Map
 }
 
-func (osc *ossSourceClient) GetContentLength(url string, header map[string]string) (int64, error) {
-	resHeader, err := osc.getMeta(url, header)
+func (osc *ossSourceClient) Download(ctx context.Context, url string, header source.Header) (io.ReadCloser, error) {
+	panic("implement me")
+}
+
+func (osc *ossSourceClient) GetExpireInfo(ctx context.Context, url string, header source.Header) (map[string]string, error) {
+	panic("implement me")
+}
+
+func (osc *ossSourceClient) GetContentLength(ctx context.Context, url string, header source.Header) (int64, error) {
+	resHeader, err := osc.getMeta(ctx, url, header)
 	if err != nil {
 		return -1, err
 	}
@@ -72,22 +90,22 @@ func (osc *ossSourceClient) GetContentLength(url string, header map[string]strin
 	return contentLen, nil
 }
 
-func (osc *ossSourceClient) IsSupportRange(url string, header map[string]string) (bool, error) {
-	_, err := osc.getMeta(url, header)
+func (osc *ossSourceClient) IsSupportRange(ctx context.Context, url string, header source.Header) (bool, error) {
+	_, err := osc.getMeta(ctx, url, header)
 	if err != nil {
 		return false, err
 	}
 	return true, nil
 }
 
-func (osc *ossSourceClient) IsExpired(url string, header, expireInfo map[string]string) (bool, error) {
+func (osc *ossSourceClient) IsExpired(ctx context.Context, url string, header source.Header, expireInfo map[string]string) (bool, error) {
 	lastModified := expireInfo[oss.HTTPHeaderLastModified]
 	eTag := expireInfo[oss.HTTPHeaderEtag]
 	if stringutils.IsBlank(lastModified) && stringutils.IsBlank(eTag) {
 		return true, nil
 	}
 
-	resHeader, err := osc.getMeta(url, header)
+	resHeader, err := osc.getMeta(ctx, url, header)
 	if err != nil {
 		return false, err
 	}
@@ -95,8 +113,8 @@ func (osc *ossSourceClient) IsExpired(url string, header, expireInfo map[string]
 		HTTPHeaderEtag], nil
 }
 
-func (osc *ossSourceClient) Download(url string, header map[string]string) (io.ReadCloser, map[string]string, error) {
-	ossObject, err := ParseOSSObject(url)
+func (osc *ossSourceClient) DownloadWithExpire(ctx context.Context, url string, header source.Header) (io.ReadCloser, map[string]string, error) {
+	ossObject, err := parseOssObject(url)
 	if err != nil {
 		return nil, nil, errors.Wrapf(err, "failed to parse oss object from url:%s", url)
 	}
@@ -137,23 +155,28 @@ func (osc *ossSourceClient) getClient(header map[string]string) (*oss.Client, er
 	if !ok {
 		return nil, errors.Wrapf(cdnerrors.ErrInvalidValue, "accessKeySecret is empty")
 	}
-	if client, ok := osc.clientMap.Load(endpoint); ok {
+	clientKey := genClientKey(endpoint, accessKeyID, accessKeySecret)
+	if client, ok := osc.clientMap.Load(clientKey); ok {
 		return client.(*oss.Client), nil
 	}
 	client, err := oss.New(endpoint, accessKeyID, accessKeySecret)
 	if err != nil {
 		return nil, err
 	}
-	osc.clientMap.Store(endpoint, client)
+	osc.clientMap.Store(clientKey, client)
 	return client, nil
 }
 
-func (osc *ossSourceClient) getMeta(url string, header map[string]string) (http.Header, error) {
+func genClientKey(endpoint, accessKeyID, accessKeySecret string) string {
+	return fmt.Sprintf("%s_%s_%s", endpoint, accessKeyID, accessKeySecret)
+}
+
+func (osc *ossSourceClient) getMeta(ctx context.Context, url string, header map[string]string) (http.Header, error) {
 	client, err := osc.getClient(header)
 	if err != nil {
 		return nil, errors.Wrapf(err, "failed to get oss client")
 	}
-	ossObject, err := ParseOSSObject(url)
+	ossObject, err := parseOssObject(url)
 	if err != nil {
 		return nil, errors.Wrapf(cdnerrors.ErrURLNotReachable, "failed to parse oss object: %v", err)
 	}
@@ -183,21 +206,23 @@ func getOptions(header map[string]string) []oss.Option {
 	return opts
 }
 
-type OSSObject struct {
-	bucket string
-	object string
+type ossObject struct {
+	endpoint string
+	bucket   string
+	object   string
 }
 
-func ParseOSSObject(ossURL string) (*OSSObject, error) {
-	url, err := url.Parse(ossURL)
-	if url.Scheme != "oss" {
-		return nil, fmt.Errorf("url:%s is not oss object", ossURL)
-	}
+func parseOssObject(rawURL string) (*ossObject, error) {
+	parsedURL, err := url.Parse(rawURL)
 	if err != nil {
-		return nil, err
+		return nil, errors.Wrapf(err, "parse rawURL: %s failed", rawURL)
+	}
+	if parsedURL.Scheme != "oss" {
+		return nil, fmt.Errorf("rawUrl:%s is not oss url", rawURL)
 	}
-	return &OSSObject{
-		bucket: url.Host,
-		object: url.Path[1:],
+	return &ossObject{
+		endpoint: parsedURL.Path[0:2],
+		bucket:   parsedURL.Host,
+		object:   parsedURL.Path[1:],
 	}, nil
 }
diff --git a/pkg/source/source_client.go b/pkg/source/source_client.go
new file mode 100644
index 00000000..cc950a7c
--- /dev/null
+++ b/pkg/source/source_client.go
@@ -0,0 +1,202 @@
+/*
+ *     Copyright 2020 The Dragonfly Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+//go:generate mockgen -destination ./mock/mock_source_client.go -package mock d7y.io/dragonfly/v2/pkg/source ResourceClient
+
+package source
+
+import (
+	"context"
+	"fmt"
+	"io"
+	"net/url"
+	"strings"
+	"time"
+
+	logger "d7y.io/dragonfly/v2/pkg/dflog"
+)
+
+var _ ResourceClient = (*ClientManagerImpl)(nil)
+var _ ClientManager = (*ClientManagerImpl)(nil)
+
+type Header map[string]string
+
+// ResourceClient supply apis that interact with the source.
+type ResourceClient interface {
+
+	// GetContentLength get length of resource content
+	// return -l if request fail
+	// return -1 if response status is not StatusOK and StatusPartialContent
+	GetContentLength(ctx context.Context, url string, header Header) (int64, error)
+
+	// IsSupportRange checks if resource supports breakpoint continuation
+	IsSupportRange(ctx context.Context, url string, header Header) (bool, error)
+
+	// IsExpired checks if a resource received or stored is the same.
+	IsExpired(ctx context.Context, url string, header Header, expireInfo map[string]string) (bool, error)
+
+	// Download download from source
+	Download(ctx context.Context, url string, header Header) (io.ReadCloser, error)
+
+	// DownloadWithExpire download from source and get expire info
+	DownloadWithExpire(ctx context.Context, url string, header Header) (io.ReadCloser, map[string]string, error)
+
+	// GetExpireInfo get expire info of resource
+	GetExpireInfo(ctx context.Context, url string, header Header) (map[string]string, error)
+}
+
+type ClientManager interface {
+	ResourceClient
+	Register(schema string, resourceClient ResourceClient)
+	UnRegister(schema string)
+}
+
+type ClientManagerImpl struct {
+	clients map[string]ResourceClient
+}
+
+var defaultMgr = &ClientManagerImpl{
+	clients: make(map[string]ResourceClient),
+}
+
+func (clientMgr *ClientManagerImpl) GetContentLength(ctx context.Context, url string, header Header) (int64, error) {
+	sourceClient, err := clientMgr.getSourceClient(url)
+	if err != nil {
+		return -1, err
+	}
+	if _, ok := ctx.Deadline(); !ok {
+		var cancel context.CancelFunc
+		ctx, cancel = context.WithTimeout(ctx, 10*time.Second)
+		defer cancel()
+	}
+	return sourceClient.GetContentLength(ctx, url, header)
+}
+
+func (clientMgr *ClientManagerImpl) IsSupportRange(ctx context.Context, url string, header Header) (bool, error) {
+	sourceClient, err := clientMgr.getSourceClient(url)
+	if err != nil {
+		return false, err
+	}
+	if _, ok := ctx.Deadline(); !ok {
+		var cancel context.CancelFunc
+		ctx, cancel = context.WithTimeout(ctx, 10*time.Second)
+		defer cancel()
+	}
+	return sourceClient.IsSupportRange(ctx, url, header)
+}
+
+func (clientMgr *ClientManagerImpl) IsExpired(ctx context.Context, url string, header Header, expireInfo map[string]string) (bool, error) {
+	sourceClient, err := clientMgr.getSourceClient(url)
+	if err != nil {
+		return false, err
+	}
+	if _, ok := ctx.Deadline(); !ok {
+		var cancel context.CancelFunc
+		ctx, cancel = context.WithTimeout(ctx, 10*time.Second)
+		defer cancel()
+	}
+	return sourceClient.IsExpired(ctx, url, header, expireInfo)
+}
+
+func (clientMgr *ClientManagerImpl) Download(ctx context.Context, url string, header Header) (io.ReadCloser, error) {
+	sourceClient, err := clientMgr.getSourceClient(url)
+	if err != nil {
+		return nil, err
+	}
+	return sourceClient.Download(ctx, url, header)
+}
+
+func (clientMgr *ClientManagerImpl) DownloadWithExpire(ctx context.Context, url string, header Header) (io.ReadCloser, map[string]string, error) {
+	sourceClient, err := clientMgr.getSourceClient(url)
+	if err != nil {
+		return nil, nil, err
+	}
+	return sourceClient.DownloadWithExpire(ctx, url, header)
+}
+
+func (clientMgr *ClientManagerImpl) GetExpireInfo(ctx context.Context, url string, header Header) (map[string]string, error) {
+	sourceClient, err := clientMgr.getSourceClient(url)
+	if err != nil {
+		return nil, err
+	}
+	if _, ok := ctx.Deadline(); !ok {
+		var cancel context.CancelFunc
+		ctx, cancel = context.WithTimeout(ctx, 10*time.Second)
+		defer cancel()
+	}
+	return sourceClient.GetExpireInfo(ctx, url, header)
+}
+
+func NewManager() ClientManager {
+	return &ClientManagerImpl{
+		clients: make(map[string]ResourceClient),
+	}
+}
+
+func (clientMgr *ClientManagerImpl) Register(schema string, resourceClient ResourceClient) {
+	if client, ok := clientMgr.clients[strings.ToLower(schema)]; ok {
+		logger.Infof("replace client %#v with %#v for schema %s", client, resourceClient, schema)
+	}
+	clientMgr.clients[strings.ToLower(schema)] = resourceClient
+}
+
+func Register(schema string, resourceClient ResourceClient) {
+	defaultMgr.Register(schema, resourceClient)
+}
+
+func (clientMgr *ClientManagerImpl) UnRegister(schema string) {
+	if client, ok := clientMgr.clients[strings.ToLower(schema)]; ok {
+		logger.Infof("remove client %#v for schema %s", client, schema)
+	}
+	delete(clientMgr.clients, strings.ToLower(schema))
+}
+
+func UnRegister(schema string) {
+	defaultMgr.UnRegister(schema)
+}
+
+func GetContentLength(ctx context.Context, url string, header Header) (int64, error) {
+	return defaultMgr.GetContentLength(ctx, url, header)
+}
+
+func IsSupportRange(ctx context.Context, url string, header Header) (bool, error) {
+	return defaultMgr.IsSupportRange(ctx, url, header)
+}
+
+func IsExpired(ctx context.Context, url string, header Header, expireInfo map[string]string) (bool, error) {
+	return defaultMgr.IsExpired(ctx, url, header, expireInfo)
+}
+
+func Download(ctx context.Context, url string, header Header) (io.ReadCloser, error) {
+	return defaultMgr.Download(ctx, url, header)
+}
+
+func DownloadWithExpire(ctx context.Context, url string, header Header) (io.ReadCloser, map[string]string, error) {
+	return defaultMgr.DownloadWithExpire(ctx, url, header)
+}
+
+// getSourceClient get a source client from source manager with specified schema.
+func (clientMgr *ClientManagerImpl) getSourceClient(rawURL string) (ResourceClient, error) {
+	logger.Debugf("current clients:%v", clientMgr.clients)
+	parsedURL, err := url.Parse(rawURL)
+	if err != nil {
+		return nil, err
+	}
+	client, ok := clientMgr.clients[strings.ToLower(parsedURL.Scheme)]
+	if !ok || client == nil {
+		return nil, fmt.Errorf("can not find client for supporting url %s, clients:%v", rawURL, clientMgr.clients)
+	}
+	return client, nil
+}
diff --git a/pkg/util/timeutils/time_utils.go b/pkg/util/timeutils/time_utils.go
index 4bd72dcd..be86e110 100644
--- a/pkg/util/timeutils/time_utils.go
+++ b/pkg/util/timeutils/time_utils.go
@@ -39,3 +39,8 @@ func UnixMillis(timeString string) int64 {
 
 	return t.UnixNano() / time.Millisecond.Nanoseconds()
 }
+
+// MillisUnixTime converts an int64 milliseconds to a unixTime
+func MillisUnixTime(millis int64) time.Time {
+	return time.Unix(millis/1000, millis%1000*int64(time.Millisecond))
+}


Test patch:
diff --git a/cdnsystem/daemon/mgr/cdn/cache_writer.go b/cdnsystem/daemon/cdn/cache_writer.go
similarity index 100%
rename from cdnsystem/daemon/mgr/cdn/cache_writer.go
rename to cdnsystem/daemon/cdn/cache_writer.go
diff --git a/cdnsystem/daemon/cdn/cache_writer_test.go b/cdnsystem/daemon/cdn/cache_writer_test.go
new file mode 100644
index 00000000..11bf1b6f
--- /dev/null
+++ b/cdnsystem/daemon/cdn/cache_writer_test.go
@@ -0,0 +1,108 @@
+/*
+ *     Copyright 2020 The Dragonfly Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cdn
+
+import (
+	"bytes"
+	"context"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"strings"
+	"testing"
+
+	"d7y.io/dragonfly/v2/cdnsystem/config"
+	"d7y.io/dragonfly/v2/cdnsystem/daemon/cdn/storage"
+	"d7y.io/dragonfly/v2/cdnsystem/daemon/progress"
+	"d7y.io/dragonfly/v2/cdnsystem/plugins"
+	"d7y.io/dragonfly/v2/cdnsystem/types"
+	"github.com/stretchr/testify/suite"
+)
+
+func TestCacheWriterSuite(t *testing.T) {
+	suite.Run(t, new(CacheWriterTestSuite))
+}
+
+type CacheWriterTestSuite struct {
+	workHome string
+	writer   *cacheWriter
+	cfg      *config.Config
+	suite.Suite
+}
+
+func (s *CacheWriterTestSuite) SetupSuite() {
+	s.workHome, _ = ioutil.TempDir("/tmp", "cdn-CacheWriterTestSuite-")
+	plugins.Initialize(config.NewDefaultPlugins())
+	storeMgr, ok := storage.Get(config.DefaultStorageMode)
+	if !ok {
+		s.Failf("failed to get storage mode %s", config.DefaultStorageMode)
+	}
+	progressMgr, _ := progress.NewManager()
+	cacheDataManager := newCacheDataManager(storeMgr)
+	cdnReporter := newReporter(progressMgr)
+	s.writer = newCacheWriter(cdnReporter, cacheDataManager)
+}
+
+func (s *CacheWriterTestSuite) TeardownSuite() {
+	if s.workHome != "" {
+		if err := os.RemoveAll(s.workHome); err != nil {
+			fmt.Printf("remove path: %s error", s.workHome)
+		}
+	}
+}
+
+func (s *CacheWriterTestSuite) TestStartWriter() {
+	testStr := "hello dragonfly2"
+	var httpFileLen = int64(len(testStr))
+	f := strings.NewReader(testStr)
+
+	task := &types.SeedTask{
+		TaskID: "5806501cbcc3bb92f0b645918c5a4b15495a63259e3e0363008f97e186509e9e",
+	}
+
+	detectResult := &cacheResult{
+		breakPoint:       0,
+		pieceMetaRecords: nil,
+		fileMetaData:     nil,
+		fileMd5:          nil,
+	}
+
+	downloadMetadata, err := s.writer.startWriter(context.TODO(), f, task, detectResult)
+	s.Nil(err)
+	s.Equal(httpFileLen, downloadMetadata.realSourceFileLength)
+}
+
+func (s *CacheWriterTestSuite) TestWriteToFile() {
+	testStr := "hello dragonfly"
+
+	var bb = bytes.NewBufferString(testStr)
+
+	task := &types.SeedTask{
+		TaskID: "5816501cbcc3bb92f0b645918c5a4b15495a63259e3e0363008f97e186509e9e",
+	}
+
+	err := s.writer.writeToFile(task.TaskID, bb, 0, nil)
+	s.Nil(err)
+
+	s.checkFileSize(s.writer.cacheDataManager.storage, task.TaskID, int64(len(testStr)))
+}
+
+func (s *CacheWriterTestSuite) checkFileSize(cdnStore storage.Manager, taskID string, expectedSize int64) {
+	storageInfo, err := cdnStore.StatDownloadFile(taskID)
+	s.Nil(err)
+	s.Equal(expectedSize, storageInfo.Size)
+}
diff --git a/cdnsystem/daemon/mgr/cdn/cache_writer_test.go b/cdnsystem/daemon/mgr/cdn/cache_writer_test.go
deleted file mode 100644
index 8a753ce9..00000000
--- a/cdnsystem/daemon/mgr/cdn/cache_writer_test.go
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- *     Copyright 2020 The Dragonfly Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package cdn
-
-import (
-	"fmt"
-	"io/ioutil"
-	"os"
-	"testing"
-
-	"github.com/stretchr/testify/suite"
-)
-
-func TestCacheWriter(t *testing.T) {
-	suite.Run(t, new(CacheWriterTestSuite))
-}
-
-type CacheWriterTestSuite struct {
-	workHome string
-	config   string
-	writer   *cacheWriter
-	suite.Suite
-}
-
-func (s *CacheWriterTestSuite) SetupSuite() {
-	s.workHome, _ = ioutil.TempDir("/tmp", "cdn-CacheWriterTestSuite-")
-	s.config = "baseDir: " + s.workHome
-	s.writer = newCacheWriter(nil, nil)
-}
-
-func (s *CacheWriterTestSuite) TeardownSuite() {
-	if s.workHome != "" {
-		if err := os.RemoveAll(s.workHome); err != nil {
-			fmt.Printf("remove path: %s error", s.workHome)
-		}
-	}
-}
-
-func (s *CacheWriterTestSuite) TestStartWriter() {
-
-}
-
-func (s *CacheWriterTestSuite) TestWriteToFile() {
-
-}
-
-func (s *CacheWriterTestSuite) TestAppendPieceMetaDataToFile() {
-
-}
diff --git a/cdnsystem/cdnerrors/errors_test.go b/cdnsystem/errors/errors_test.go
similarity index 99%
rename from cdnsystem/cdnerrors/errors_test.go
rename to cdnsystem/errors/errors_test.go
index 168ec103..0b3f3598 100644
--- a/cdnsystem/cdnerrors/errors_test.go
+++ b/cdnsystem/errors/errors_test.go
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package cdnerrors
+package errors
 
 import (
 	"testing"
@@ -23,7 +23,7 @@ import (
 	"github.com/stretchr/testify/suite"
 )
 
-func TestErrorTestSuite(t *testing.T) {
+func TestErrorSuite(t *testing.T) {
 	suite.Run(t, new(ErrorTestSuite))
 }
 
diff --git a/cdnsystem/plugins/plugins_test.go b/cdnsystem/plugins/plugins_test.go
index 37ba2889..310f6d4f 100644
--- a/cdnsystem/plugins/plugins_test.go
+++ b/cdnsystem/plugins/plugins_test.go
@@ -44,19 +44,7 @@ func (s *PluginsTestSuite) TearDownTest() {
 	mgr = s.mgr
 }
 
-func (s *PluginsTestSuite) TestSetManager() {
-	tmp := &managerIml{}
-	SetManager(tmp)
-	s.Equal(mgr, tmp)
-}
-
-// -----------------------------------------------------------------------------
-
-func (s *PluginsTestSuite) TestInitialize() {
-
-}
-
-func (s *PluginsTestSuite) TestManagerIml_Builder() {
+func (s *PluginsTestSuite) TestPluginBuilder() {
 	var builder Builder = func(conf interface{}) (plugin Plugin, e error) {
 		return nil, nil
 	}
@@ -64,7 +52,7 @@ func (s *PluginsTestSuite) TestManagerIml_Builder() {
 
 	var testFunc = func(pt PluginType, name string, b Builder, result bool) {
 		manager.AddBuilder(pt, name, b)
-		obj := manager.GetBuilder(pt, name)
+		obj, _ := manager.GetBuilder(pt, name)
 		if result {
 			s.NotNil(obj)
 			objVal := reflect.ValueOf(obj)
@@ -76,7 +64,6 @@ func (s *PluginsTestSuite) TestManagerIml_Builder() {
 		}
 	}
 
-	testFunc(PluginType("test"), "test", builder, false)
 	for _, pt := range PluginTypes {
 		testFunc(pt, "test", builder, true)
 		testFunc(pt, "", nil, false)
@@ -85,12 +72,12 @@ func (s *PluginsTestSuite) TestManagerIml_Builder() {
 	}
 }
 
-func (s *PluginsTestSuite) TestManagerIml_Plugin() {
+func (s *PluginsTestSuite) TestManagerPlugin() {
 	manager := NewManager()
 
 	var testFunc = func(p Plugin, result bool) {
 		manager.AddPlugin(p)
-		obj := manager.GetPlugin(p.Type(), p.Name())
+		obj, _ := manager.GetPlugin(p.Type(), p.Name())
 		if result {
 			s.NotNil(obj)
 			s.Equal(obj, p)
@@ -142,12 +129,12 @@ func (s *PluginsTestSuite) TestRepositoryIml() {
 	repo := NewRepository()
 	for _, v := range cases {
 		repo.Add(v.pt, v.name, v.data)
-		data := repo.Get(v.pt, v.name)
+		data, _ := repo.Get(v.pt, v.name)
 		if v.addResult {
 			s.NotNil(data)
 			s.Equal(data, v.data)
 			repo.Delete(v.pt, v.name)
-			data = repo.Get(v.pt, v.name)
+			data, _ = repo.Get(v.pt, v.name)
 			s.Nil(data)
 		} else {
 			s.Nil(data)
diff --git a/cdnsystem/server/service/cdn_seed_server_test.go b/cdnsystem/server/service/cdn_seed_server_test.go
new file mode 100644
index 00000000..cb9325b4
--- /dev/null
+++ b/cdnsystem/server/service/cdn_seed_server_test.go
@@ -0,0 +1,189 @@
+/*
+ *     Copyright 2020 The Dragonfly Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package service
+
+import (
+	"context"
+	"reflect"
+	"testing"
+
+	"d7y.io/dragonfly/v2/cdnsystem/config"
+	"d7y.io/dragonfly/v2/cdnsystem/daemon"
+	"d7y.io/dragonfly/v2/cdnsystem/types"
+	"d7y.io/dragonfly/v2/pkg/rpc/base"
+	"d7y.io/dragonfly/v2/pkg/rpc/cdnsystem"
+)
+
+func TestCdnSeedServer_GetPieceTasks(t *testing.T) {
+	type fields struct {
+		taskMgr daemon.SeedTaskMgr
+		cfg     *config.Config
+	}
+	type args struct {
+		ctx context.Context
+		req *base.PieceTaskRequest
+	}
+	tests := []struct {
+		name            string
+		fields          fields
+		args            args
+		wantPiecePacket *base.PiecePacket
+		wantErr         bool
+	}{
+		// TODO: Add test cases.
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			css := &CdnSeedServer{
+				taskMgr: tt.fields.taskMgr,
+				cfg:     tt.fields.cfg,
+			}
+			gotPiecePacket, err := css.GetPieceTasks(tt.args.ctx, tt.args.req)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("GetPieceTasks() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(gotPiecePacket, tt.wantPiecePacket) {
+				t.Errorf("GetPieceTasks() gotPiecePacket = %v, want %v", gotPiecePacket, tt.wantPiecePacket)
+			}
+		})
+	}
+}
+
+func TestCdnSeedServer_ObtainSeeds(t *testing.T) {
+	type fields struct {
+		taskMgr daemon.SeedTaskMgr
+		cfg     *config.Config
+	}
+	type args struct {
+		ctx context.Context
+		req *cdnsystem.SeedRequest
+		psc chan<- *cdnsystem.PieceSeed
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		args    args
+		wantErr bool
+	}{
+		// TODO: Add test cases.
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			css := &CdnSeedServer{
+				taskMgr: tt.fields.taskMgr,
+				cfg:     tt.fields.cfg,
+			}
+			if err := css.ObtainSeeds(tt.args.ctx, tt.args.req, tt.args.psc); (err != nil) != tt.wantErr {
+				t.Errorf("ObtainSeeds() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
+
+func TestNewCdnSeedServer(t *testing.T) {
+	type args struct {
+		cfg     *config.Config
+		taskMgr daemon.SeedTaskMgr
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    *CdnSeedServer
+		wantErr bool
+	}{
+		// TODO: Add test cases.
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := NewCdnSeedServer(tt.args.cfg, tt.args.taskMgr)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("NewCdnSeedServer() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("NewCdnSeedServer() got = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func Test_checkPieceTasksRequestParams(t *testing.T) {
+	type args struct {
+		req *base.PieceTaskRequest
+	}
+	tests := []struct {
+		name    string
+		args    args
+		wantErr bool
+	}{
+		// TODO: Add test cases.
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if err := checkPieceTasksRequestParams(tt.args.req); (err != nil) != tt.wantErr {
+				t.Errorf("checkPieceTasksRequestParams() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
+
+func Test_checkSeedRequestParams(t *testing.T) {
+	type args struct {
+		req *cdnsystem.SeedRequest
+	}
+	tests := []struct {
+		name    string
+		args    args
+		wantErr bool
+	}{
+		// TODO: Add test cases.
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if err := checkSeedRequestParams(tt.args.req); (err != nil) != tt.wantErr {
+				t.Errorf("checkSeedRequestParams() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
+
+func Test_constructRegisterRequest(t *testing.T) {
+	type args struct {
+		req *cdnsystem.SeedRequest
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    *types.TaskRegisterRequest
+		wantErr bool
+	}{
+		// TODO: Add test cases.
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := constructRegisterRequest(tt.args.req)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("constructRegisterRequest() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("constructRegisterRequest() got = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/cdnsystem/storedriver/local/local_driver_test.go b/cdnsystem/storedriver/local/local_driver_test.go
index a6feb963..c6525cac 100644
--- a/cdnsystem/storedriver/local/local_driver_test.go
+++ b/cdnsystem/storedriver/local/local_driver_test.go
@@ -17,57 +17,69 @@
 package local
 
 import (
-	"context"
 	"io"
 	"io/ioutil"
 	"os"
 	"path/filepath"
 	"strings"
 	"sync"
+	"syscall"
 	"testing"
 
-	"d7y.io/dragonfly/v2/cdnsystem/cdnerrors"
+	"d7y.io/dragonfly/v2/cdnsystem/errors"
+	"d7y.io/dragonfly/v2/cdnsystem/plugins"
 	"d7y.io/dragonfly/v2/cdnsystem/storedriver"
 	"d7y.io/dragonfly/v2/pkg/unit"
 	"d7y.io/dragonfly/v2/pkg/util/statutils"
 	"github.com/stretchr/testify/suite"
 )
 
-func TestStorageSuite(t *testing.T) {
-	suite.Run(t, new(StorageTestSuite))
+func TestLocalDriverTestSuite(t *testing.T) {
+	suite.Run(t, new(LocalDriverTestSuite))
 }
 
-type StorageTestSuite struct {
+type LocalDriverTestSuite struct {
+	suite.Suite
 	workHome string
 	storedriver.Driver
-	suite.Suite
 }
 
-func (s *StorageTestSuite) SetupSuite() {
-	s.workHome, _ = ioutil.TempDir("/tmp", "cdn-StoreTestSuite-repo")
-	store, err := NewStorageDriver(&storedriver.Config{
-		BaseDir: "/tmp/download"})
-	s.Nil(err)
-	s.NotNil(store)
-	s.Driver = store
+func (s *LocalDriverTestSuite) SetupSuite() {
+	s.workHome, _ = ioutil.TempDir("/tmp", "cdn-local-driver-repo")
+	pluginProps := map[plugins.PluginType][]*plugins.PluginProperties{
+		plugins.StorageDriverPlugin: {
+			&plugins.PluginProperties{
+				Name:   DiskDriverName,
+				Enable: true,
+				Config: map[string]string{"baseDir": filepath.Join(s.workHome, "repo")},
+			},
+		},
+	}
+	s.Nil(plugins.Initialize(pluginProps))
+	gotDriver, ok := storedriver.Get(DiskDriverName)
+	s.True(ok)
+	s.Equal(&driver{BaseDir: filepath.Join(s.workHome, "repo")}, gotDriver)
+	s.Driver = gotDriver
 }
 
-func (s *StorageTestSuite) TeardownSuite() {
+func (s *LocalDriverTestSuite) TeardownSuite() {
 	if s.workHome != "" {
-		os.RemoveAll(s.workHome)
+		s.Nil(os.RemoveAll(s.workHome))
 	}
 }
 
-func (s *StorageTestSuite) TestGetPutBytes() {
+func (s *LocalDriverTestSuite) TestGetPutBytes() {
 	var cases = []struct {
 		name        string
 		putRaw      *storedriver.Raw
 		getRaw      *storedriver.Raw
 		data        []byte
 		getErrCheck func(error) bool
+		putErrCheck func(error) bool
 		expected    string
 	}{
 		{
+			name: "get put full",
 			putRaw: &storedriver.Raw{
 				Bucket: "GetPut",
 				Key:    "foo1",
@@ -77,9 +89,11 @@ func (s *StorageTestSuite) TestGetPutBytes() {
 				Key:    "foo1",
 			},
 			data:        []byte("hello foo"),
-			getErrCheck: isNilError,
+			putErrCheck: isNil,
+			getErrCheck: isNil,
 			expected:    "hello foo",
 		}, {
+			name: "get specific length",
 			putRaw: &storedriver.Raw{
 				Bucket: "GetPut",
 				Key:    "foo2",
@@ -90,10 +104,12 @@ func (s *StorageTestSuite) TestGetPutBytes() {
 				Offset: 0,
 				Length: 5,
 			},
-			getErrCheck: isNilError,
+			putErrCheck: isNil,
+			getErrCheck: isNil,
 			data:        []byte("hello foo"),
 			expected:    "hello",
 		}, {
+			name: "get full length",
 			putRaw: &storedriver.Raw{
 				Bucket: "GetPut",
 				Key:    "foo3",
@@ -104,10 +120,12 @@ func (s *StorageTestSuite) TestGetPutBytes() {
 				Offset: 0,
 				Length: 0,
 			},
-			getErrCheck: isNilError,
+			putErrCheck: isNil,
+			getErrCheck: isNil,
 			data:        []byte("hello foo"),
 			expected:    "hello foo",
 		}, {
+			name: "get invalid length",
 			putRaw: &storedriver.Raw{
 				Bucket: "GetPut",
 				Key:    "foo4",
@@ -118,10 +136,12 @@ func (s *StorageTestSuite) TestGetPutBytes() {
 				Offset: 0,
 				Length: -1,
 			},
-			getErrCheck: cdnerrors.IsInvalidValue,
+			putErrCheck: isNil,
+			getErrCheck: errors.IsInvalidValue,
 			data:        []byte("hello foo"),
 			expected:    "",
 		}, {
+			name: "put specific length",
 			putRaw: &storedriver.Raw{
 				Bucket: "GetPut",
 				Key:    "foo5",
@@ -131,10 +151,12 @@ func (s *StorageTestSuite) TestGetPutBytes() {
 				Bucket: "GetPut",
 				Key:    "foo5",
 			},
-			getErrCheck: isNilError,
+			putErrCheck: isNil,
+			getErrCheck: isNil,
 			data:        []byte("hello foo"),
 			expected:    "hello",
 		}, {
+			name: "get invalid offset",
 			putRaw: &storedriver.Raw{
 				Bucket: "GetPut",
 				Key:    "foo6",
@@ -145,10 +167,11 @@ func (s *StorageTestSuite) TestGetPutBytes() {
 				Offset: -1,
 			},
 			data:        []byte("hello foo"),
-			getErrCheck: cdnerrors.IsInvalidValue,
+			putErrCheck: isNil,
+			getErrCheck: errors.IsInvalidValue,
 			expected:    "",
 		}, {
-			name: "offset不从0开始",
+			name: "put/get data from specific offset",
 			putRaw: &storedriver.Raw{
 				Bucket: "GetPut",
 				Key:    "foo7",
@@ -160,7 +183,8 @@ func (s *StorageTestSuite) TestGetPutBytes() {
 				Offset: 3,
 			},
 			data:        []byte("hello foo"),
-			getErrCheck: isNilError,
+			putErrCheck: isNil,
+			getErrCheck: isNil,
 			expected:    "hello foo",
 		},
 	}
@@ -169,8 +193,7 @@ func (s *StorageTestSuite) TestGetPutBytes() {
 		s.Run(v.name, func() {
 			// put
 			err := s.PutBytes(v.putRaw, v.data)
-			s.Nil(err)
-
+			s.True(v.putErrCheck(err))
 			// get
 			result, err := s.GetBytes(v.getRaw)
 			s.True(v.getErrCheck(err))
@@ -183,7 +206,7 @@ func (s *StorageTestSuite) TestGetPutBytes() {
 	}
 }
 
-func (s *StorageTestSuite) TestGetPut() {
+func (s *LocalDriverTestSuite) TestGetPut() {
 	var cases = []struct {
 		name        string
 		putRaw      *storedriver.Raw
@@ -201,7 +224,7 @@ func (s *StorageTestSuite) TestGetPut() {
 				Key: "foo0.meta",
 			},
 			data:        strings.NewReader("hello meta file"),
-			getErrCheck: isNilError,
+			getErrCheck: isNil,
 			expected:    "hello meta file",
 		}, {
 			putRaw: &storedriver.Raw{
@@ -211,7 +234,7 @@ func (s *StorageTestSuite) TestGetPut() {
 				Key: "foo1.meta",
 			},
 			data:        strings.NewReader("hello meta file"),
-			getErrCheck: isNilError,
+			getErrCheck: isNil,
 			expected:    "hello meta file",
 		}, {
 			putRaw: &storedriver.Raw{
@@ -222,7 +245,7 @@ func (s *StorageTestSuite) TestGetPut() {
 				Key: "foo2.meta",
 			},
 			data:        strings.NewReader("hello meta file"),
-			getErrCheck: isNilError,
+			getErrCheck: isNil,
 			expected:    "hello ",
 		}, {
 			putRaw: &storedriver.Raw{
@@ -234,7 +257,7 @@ func (s *StorageTestSuite) TestGetPut() {
 				Length: 5,
 			},
 			data:        strings.NewReader("hello meta file"),
-			getErrCheck: isNilError,
+			getErrCheck: isNil,
 			expected:    "llo m",
 		}, {
 			putRaw: &storedriver.Raw{
@@ -245,7 +268,7 @@ func (s *StorageTestSuite) TestGetPut() {
 				Offset: 2,
 				Length: -1,
 			},
-			getErrCheck: cdnerrors.IsInvalidValue,
+			getErrCheck: errors.IsInvalidValue,
 			data:        strings.NewReader("hello meta file"),
 			expected:    "",
 		}, {
@@ -257,7 +280,7 @@ func (s *StorageTestSuite) TestGetPut() {
 				Offset: 30,
 				Length: 5,
 			},
-			getErrCheck: cdnerrors.IsInvalidValue,
+			getErrCheck: errors.IsInvalidValue,
 			data:        strings.NewReader("hello meta file"),
 			expected:    "",
 		},
@@ -286,7 +309,7 @@ func (s *StorageTestSuite) TestGetPut() {
 	}
 }
 
-func (s *StorageTestSuite) TestAppendBytes() {
+func (s *LocalDriverTestSuite) TestAppendBytes() {
 	var cases = []struct {
 		name        string
 		putRaw      *storedriver.Raw
@@ -298,13 +321,13 @@ func (s *StorageTestSuite) TestAppendBytes() {
 		expected    string
 	}{
 		{
+			name: "foo0.meta",
 			putRaw: &storedriver.Raw{
 				Key:    "foo0.meta",
 				Length: 13,
 			},
 			appendRaw: &storedriver.Raw{
 				Key:    "foo0.meta",
-				Offset: 2,
 				Length: 3,
 				Append: true,
 			},
@@ -313,15 +336,15 @@ func (s *StorageTestSuite) TestAppendBytes() {
 			},
 			data:        strings.NewReader("hello meta fi"),
 			appData:     strings.NewReader("append data"),
-			getErrCheck: isNilError,
+			getErrCheck: isNil,
 			expected:    "hello meta fiapp",
 		}, {
+			name: "foo1.meta",
 			putRaw: &storedriver.Raw{
 				Key: "foo1.meta",
 			},
 			appendRaw: &storedriver.Raw{
 				Key:    "foo1.meta",
-				Offset: 29,
 				Length: 3,
 				Append: true,
 			},
@@ -330,63 +353,26 @@ func (s *StorageTestSuite) TestAppendBytes() {
 			},
 			data:        strings.NewReader("hello meta file"),
 			appData:     strings.NewReader("append data"),
-			getErrCheck: isNilError,
+			getErrCheck: isNil,
 			expected:    "hello meta fileapp",
+		}, {
+			name: "foo2.meta",
+			putRaw: &storedriver.Raw{
+				Key:    "foo2.meta",
+				Length: 6,
+			},
+			appendRaw: &storedriver.Raw{
+				Key:    "foo0.meta",
+				Length: 3,
+				Append: true,
+			},
+			getRaw: &storedriver.Raw{
+				Key: "foo2.meta",
+			},
+			data:        strings.NewReader("hello meta file"),
+			getErrCheck: isNil,
+			expected:    "hello ",
 		},
-		//{
-		//	putRaw: &store.Raw{
-		//		Key:    "foo2.meta",
-		//		Length: 6,
-		//	},
-		//	appendRaw: &store.Raw{
-		//		Key:    "foo0.meta",
-		//		Offset: 29,
-		//		Length: 3,
-		//		Append: true,
-		//	},
-		//	getRaw: &store.Raw{
-		//		Key: "foo2.meta",
-		//	},
-		//	data:        strings.NewReader("hello meta file"),
-		//	getErrCheck: isNilError,
-		//	expected:    "hello ",
-		//}, {
-		//	putRaw: &store.Raw{
-		//		Key: "foo3.meta",
-		//	},
-		//	getRaw: &store.Raw{
-		//		Key:    "foo3.meta",
-		//		Offset: 2,
-		//		Length: 5,
-		//	},
-		//	data:        strings.NewReader("hello meta file"),
-		//	getErrCheck: isNilError,
-		//	expected:    "llo m",
-		//}, {
-		//	putRaw: &store.Raw{
-		//		Key: "foo4.meta",
-		//	},
-		//	getRaw: &store.Raw{
-		//		Key:    "foo4.meta",
-		//		Offset: 2,
-		//		Length: -1,
-		//	},
-		//	getErrCheck: cdnerrors.IsInvalidValue,
-		//	data:        strings.NewReader("hello meta file"),
-		//	expected:    "",
-		//}, {
-		//	putRaw: &store.Raw{
-		//		Key: "foo5.meta",
-		//	},
-		//	getRaw: &store.Raw{
-		//		Key:    "foo5.meta",
-		//		Offset: 30,
-		//		Length: 5,
-		//	},
-		//	getErrCheck: cdnerrors.IsInvalidValue,
-		//	data:        strings.NewReader("hello meta file"),
-		//	expected:    "",
-		//},
 	}
 	for _, v := range cases {
 		s.Run(v.name, func() {
@@ -413,7 +399,7 @@ func (s *StorageTestSuite) TestAppendBytes() {
 	}
 }
 
-func (s *StorageTestSuite) TestPutTrunc() {
+func (s *LocalDriverTestSuite) TestPutTrunc() {
 	originRaw := &storedriver.Raw{
 		Key:    "fooTrunc.meta",
 		Offset: 0,
@@ -434,7 +420,7 @@ func (s *StorageTestSuite) TestPutTrunc() {
 				Trunc:  true,
 			},
 			data:         strings.NewReader("hello"),
-			getErrCheck:  isNilError,
+			getErrCheck:  isNil,
 			expectedData: "hello",
 		}, {
 			truncRaw: &storedriver.Raw{
@@ -443,7 +429,7 @@ func (s *StorageTestSuite) TestPutTrunc() {
 				Trunc:  true,
 			},
 			data:         strings.NewReader("golang"),
-			getErrCheck:  isNilError,
+			getErrCheck:  isNil,
 			expectedData: "\x00\x00\x00\x00\x00\x00golang",
 		}, {
 			truncRaw: &storedriver.Raw{
@@ -452,7 +438,7 @@ func (s *StorageTestSuite) TestPutTrunc() {
 				Trunc:  false,
 			},
 			data:         strings.NewReader("foo"),
-			getErrCheck:  isNilError,
+			getErrCheck:  isNil,
 			expectedData: "foolo world",
 		}, {
 			truncRaw: &storedriver.Raw{
@@ -461,7 +447,7 @@ func (s *StorageTestSuite) TestPutTrunc() {
 				Trunc:  false,
 			},
 			data:         strings.NewReader("foo"),
-			getErrCheck:  isNilError,
+			getErrCheck:  isNil,
 			expectedData: "hello foold",
 		},
 	}
@@ -486,7 +472,7 @@ func (s *StorageTestSuite) TestPutTrunc() {
 	}
 }
 
-func (s *StorageTestSuite) TestPutParallel() {
+func (s *LocalDriverTestSuite) TestPutParallel() {
 	var key = "fooPutParallel"
 	var routineCount = 4
 	var testStr = "hello"
@@ -497,10 +483,10 @@ func (s *StorageTestSuite) TestPutParallel() {
 		wg.Add(1)
 		go func(i int) {
 			defer wg.Done()
-			s.Put(&storedriver.Raw{
+			s.Nil(s.Put(&storedriver.Raw{
 				Key:    key,
 				Offset: int64(i) * int64(testStrLength),
-			}, strings.NewReader(testStr))
+			}, strings.NewReader(testStr)))
 		}(k)
 	}
 	wg.Wait()
@@ -510,261 +496,70 @@ func (s *StorageTestSuite) TestPutParallel() {
 	s.Equal(info.Size, int64(routineCount)*int64(testStrLength))
 }
 
-func (s *StorageTestSuite) TestRemove() {
-	type fields struct {
-		BaseDir string
-	}
-	type args struct {
-		raw *storedriver.Raw
-	}
-	tests := []struct {
-		name    string
-		fields  fields
-		args    args
-		wantErr bool
-	}{
-		{},
-	}
-	for _, tt := range tests {
-		err := s.Remove(tt.args.raw)
-		s.Equal(err != nil, tt.wantErr)
+func (s *LocalDriverTestSuite) TestLocalDriverExitsAndRemove() {
+	raw := &storedriver.Raw{
+		Bucket: "existsTest",
+		Key:    "test",
 	}
+	s.Nil(s.PutBytes(raw, []byte("hello world")))
+	s.True(s.Exits(raw))
+	s.Nil(s.Remove(raw))
+	s.False(s.Exits(raw))
 }
 
-func (s *StorageTestSuite) TestStat() {
-	type fields struct {
-		BaseDir string
-	}
-	type args struct {
-		raw *storedriver.Raw
-	}
-	tests := []struct {
-		name    string
-		fields  fields
-		args    args
-		want    *storedriver.StorageInfo
-		wantErr bool
-	}{
-		{},
-	}
-	for _, tt := range tests {
-		got, err := s.Stat(tt.args.raw)
-		s.Equal(err, tt.wantErr)
-		s.EqualValues(got, tt.want)
-	}
+func (s *LocalDriverTestSuite) TestLocalDriverGetHomePath() {
+	s.Equal(filepath.Join(s.workHome, "repo"), s.GetHomePath())
 }
 
-func Test_diskStorage_CreateBaseDir(t *testing.T) {
-	type fields struct {
-		BaseDir string
-	}
-	type args struct {
-		ctx context.Context
-	}
-	tests := []struct {
-		name    string
-		fields  fields
-		args    args
-		wantErr bool
-	}{
-		// TODO: Add test cases.
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			ds := &driver{
-				BaseDir: tt.fields.BaseDir,
-			}
-			if err := ds.CreateBaseDir(); (err != nil) != tt.wantErr {
-				t.Errorf("CreateBaseDir() error = %v, wantErr %v", err, tt.wantErr)
-			}
-		})
+func (s *LocalDriverTestSuite) TestLocalDriverGetPath() {
+	raw := &storedriver.Raw{
+		Bucket: "dir",
+		Key:    "path",
 	}
+	path := filepath.Join(s.workHome, "repo", "dir", "path")
+	s.Equal(path, s.GetPath(raw))
 }
 
-func Test_diskStorage_Exits(t *testing.T) {
-	type fields struct {
-		BaseDir string
-	}
-	type args struct {
-		ctx context.Context
-		raw *storedriver.Raw
-	}
-	tests := []struct {
-		name   string
-		fields fields
-		args   args
-		want   bool
-	}{
-		// TODO: Add test cases.
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			ds := &driver{
-				BaseDir: tt.fields.BaseDir,
-			}
-			if got := ds.Exits(tt.args.raw); got != tt.want {
-				t.Errorf("Exits() = %v, want %v", got, tt.want)
-			}
-		})
-	}
-}
-
-func Test_diskStorage_GetHomePath(t *testing.T) {
-	type fields struct {
-		BaseDir string
-	}
-	type args struct {
-		ctx context.Context
-	}
-	tests := []struct {
-		name   string
-		fields fields
-		args   args
-		want   string
-	}{
-		// TODO: Add test cases.
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			ds := &driver{
-				BaseDir: tt.fields.BaseDir,
-			}
-			if got := ds.GetHomePath(); got != tt.want {
-				t.Errorf("GetHomePath() = %v, want %v", got, tt.want)
-			}
-		})
-	}
-}
-
-func Test_diskStorage_GetPath(t *testing.T) {
-	type fields struct {
-		BaseDir string
-	}
-	type args struct {
-		raw *storedriver.Raw
-	}
-	tests := []struct {
-		name   string
-		fields fields
-		args   args
-		want   string
-	}{
-		// TODO: Add test cases.
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			ds := &driver{
-				BaseDir: tt.fields.BaseDir,
-			}
-			if got := ds.GetPath(tt.args.raw); got != tt.want {
-				t.Errorf("GetPath() = %v, want %v", got, tt.want)
-			}
-		})
-	}
-}
-
-func Test_diskStorage_GetTotalAndFreeSpace(t *testing.T) {
-	type fields struct {
-		BaseDir string
-	}
-	type args struct {
-		ctx context.Context
-	}
-	tests := []struct {
-		name    string
-		fields  fields
-		args    args
-		want    unit.Bytes
-		want1   unit.Bytes
-		wantErr bool
-	}{
-		// TODO: Add test cases.
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			ds := &driver{
-				BaseDir: tt.fields.BaseDir,
-			}
-			got, got1, err := ds.GetTotalAndFreeSpace()
-			if (err != nil) != tt.wantErr {
-				t.Errorf("GetTotalAndFreeSpace() error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
-			if got != tt.want {
-				t.Errorf("GetTotalAndFreeSpace() got = %v, want %v", got, tt.want)
-			}
-			if got1 != tt.want1 {
-				t.Errorf("GetTotalAndFreeSpace() got1 = %v, want %v", got1, tt.want1)
-			}
-		})
-	}
-}
-
-func Test_diskStorage_GetTotalSpace(t *testing.T) {
-	type fields struct {
-		BaseDir string
-	}
-	type args struct {
-		ctx context.Context
-	}
-	tests := []struct {
-		name    string
-		fields  fields
-		args    args
-		want    unit.Bytes
-		wantErr bool
-	}{
-		// TODO: Add test cases.
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			ds := &driver{
-				BaseDir: tt.fields.BaseDir,
-			}
-			got, err := ds.GetTotalSpace()
-			if (err != nil) != tt.wantErr {
-				t.Errorf("GetTotalSpace() error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
-			if got != tt.want {
-				t.Errorf("GetTotalSpace() got = %v, want %v", got, tt.want)
-			}
-		})
-	}
+func (s *LocalDriverTestSuite) TestLocalDriverGetTotalAndFreeSpace() {
+	fs := syscall.Statfs_t{}
+	s.Nil(syscall.Statfs(s.GetHomePath(), &fs))
+	total := unit.Bytes(fs.Blocks * uint64(fs.Bsize))
+	free := unit.Bytes(fs.Bavail * uint64(fs.Bsize))
+	got, got1, err := s.GetTotalAndFreeSpace()
+	s.Nil(err)
+	s.Equal(total, got)
+	s.Equal(free, got1)
 }
 
-func (s *StorageTestSuite) BenchmarkPutParallel() {
+func (s *LocalDriverTestSuite) BenchmarkPutParallel() {
 	var wg sync.WaitGroup
 	for k := 0; k < 1000; k++ {
 		wg.Add(1)
 		go func(i int) {
 			defer wg.Done()
-			s.Put(&storedriver.Raw{
-				Key:    "foo.bech",
+			s.Nil(s.Put(&storedriver.Raw{
+				Key:    "foo.bench",
 				Offset: int64(i) * 5,
-			}, strings.NewReader("hello"))
+			}, strings.NewReader("hello")))
 		}(k)
 	}
 	wg.Wait()
 }
 
-func (s *StorageTestSuite) BenchmarkPutSerial() {
+func (s *LocalDriverTestSuite) BenchmarkPutSerial() {
 	for k := 0; k < 1000; k++ {
-		s.Put(&storedriver.Raw{
-			Key:    "foo1.bech",
+		s.Nil(s.Put(&storedriver.Raw{
+			Key:    "foo1.bench",
 			Offset: int64(k) * 5,
-		}, strings.NewReader("hello"))
+		}, strings.NewReader("hello")))
 	}
 }
 
-// -----------------------------------------------------------------------------
-// helper function
-
-func (s *StorageTestSuite) checkStat(raw *storedriver.Raw) {
+func (s *LocalDriverTestSuite) checkStat(raw *storedriver.Raw) {
 	info, err := s.Stat(raw)
-	s.Equal(isNilError(err), true)
+	s.Equal(isNil(err), true)
 
-	pathTemp := filepath.Join(s.workHome, raw.Bucket, raw.Key)
+	pathTemp := filepath.Join(s.Driver.GetHomePath(), raw.Bucket, raw.Key)
 	f, _ := os.Stat(pathTemp)
 
 	s.EqualValues(info, &storedriver.StorageInfo{
@@ -775,14 +570,14 @@ func (s *StorageTestSuite) checkStat(raw *storedriver.Raw) {
 	})
 }
 
-func (s *StorageTestSuite) checkRemove(raw *storedriver.Raw) {
+func (s *LocalDriverTestSuite) checkRemove(raw *storedriver.Raw) {
 	err := s.Remove(raw)
-	s.Equal(isNilError(err), true)
+	s.Equal(isNil(err), true)
 
 	_, err = s.Stat(raw)
-	s.Equal(cdnerrors.IsFileNotExist(err), true)
+	s.Equal(errors.IsFileNotExist(err), true)
 }
 
-func isNilError(err error) bool {
+func isNil(err error) bool {
 	return err == nil
 }
diff --git a/cdnsystem/storedriver/store_mgr_test.go b/cdnsystem/storedriver/store_mgr_test.go
index a7e47d94..f37eacce 100644
--- a/cdnsystem/storedriver/store_mgr_test.go
+++ b/cdnsystem/storedriver/store_mgr_test.go
@@ -17,74 +17,112 @@
 package storedriver
 
 import (
+	"io"
+	"os"
 	"testing"
 
-	"github.com/stretchr/testify/suite"
+	"d7y.io/dragonfly/v2/cdnsystem/plugins"
+	"d7y.io/dragonfly/v2/pkg/unit"
+	"github.com/stretchr/testify/assert"
 )
 
-func TestStoreMgrTestSuite(t *testing.T) {
-	suite.Run(t, new(StoreMgrTestSuite))
-}
-
-type StoreMgrTestSuite struct {
-	suite.Suite
-}
-
-func (s *StoreMgrTestSuite) SetupSuite() {
-	type args struct {
-		name    string
-		builder DriverBuilder
-	}
-	tests := []struct {
-		name string
-		args args
-	}{
-		{
-			name: "test1",
-			args: args{
-				name: "disk1",
-				builder: func(cfg *Config) (Driver, error) {
-					return nil, nil
-				},
-			},
-		}, {
-			name: "test2",
-			args: args{
-				name: "memory1",
-				builder: func(cfg *Config) (Driver, error) {
-					return nil, nil
+type mockDriver struct {
+	BaseDir string
+}
+
+func newDriver(cfg *Config) (Driver, error) {
+	return &mockDriver{
+		BaseDir: cfg.BaseDir,
+	}, nil
+}
+
+func (m mockDriver) Get(_ *Raw) (io.ReadCloser, error) {
+	panic("implement me")
+}
+
+func (m mockDriver) GetBytes(_ *Raw) ([]byte, error) {
+	panic("implement me")
+}
+
+func (m mockDriver) Put(_ *Raw, _ io.Reader) error {
+	panic("implement me")
+}
+
+func (m mockDriver) PutBytes(_ *Raw, _ []byte) error {
+	panic("implement me")
+}
+
+func (m mockDriver) Remove(_ *Raw) error {
+	panic("implement me")
+}
+
+func (m mockDriver) Stat(_ *Raw) (*StorageInfo, error) {
+	panic("implement me")
+}
+
+func (m mockDriver) GetAvailSpace() (unit.Bytes, error) {
+	panic("implement me")
+}
+
+func (m mockDriver) GetTotalAndFreeSpace() (unit.Bytes, unit.Bytes, error) {
+	panic("implement me")
+}
+
+func (m mockDriver) GetTotalSpace() (unit.Bytes, error) {
+	panic("implement me")
+}
+
+func (m mockDriver) Walk(_ *Raw) error {
+	panic("implement me")
+}
+
+func (m mockDriver) CreateBaseDir() error {
+	panic("implement me")
+}
+
+func (m mockDriver) GetPath(_ *Raw) string {
+	panic("implement me")
+}
+
+func (m mockDriver) MoveFile(_ string, _ string) error {
+	panic("implement me")
+}
+
+func (m mockDriver) Exits(_ *Raw) bool {
+	panic("implement me")
+}
+
+func (m mockDriver) GetHomePath() string {
+	panic("implement me")
+}
+
+func TestRegister(t *testing.T) {
+	assert := assert.New(t)
+	var pluginName = "mock"
+	var baseDir = "/tmp/drivertest"
+	var driverBuilder = newDriver
+	err := Register(pluginName, driverBuilder)
+	assert.Nil(err)
+	driver, ok := Get(pluginName)
+	assert.Nil(driver)
+	assert.Equal(false, ok)
+	// enable driver
+	err = plugins.Initialize(map[plugins.PluginType][]*plugins.PluginProperties{
+		plugins.StorageDriverPlugin: {
+			&plugins.PluginProperties{
+				Name:   pluginName,
+				Enable: true,
+				Config: &Config{
+					baseDir,
 				},
 			},
 		},
-	}
-	for _, tt := range tests {
-		Register(tt.name, tt.args.builder)
-	}
-}
-
-func (s *StoreMgrTestSuite) TestGet() {
-	type args struct {
-		name string
-	}
-	tests := []struct {
-		name    string
-		args    args
-		want    Driver
-		wantErr bool
-	}{
-		{
-			name: "test1",
-			args: args{name: "disk"},
-			want: &driverPlugin{
-				name:     "disk",
-				instance: nil,
-			},
-			wantErr: false,
-		},
-	}
-	for _, tt := range tests {
-		got, err := Get(tt.args.name)
-		s.Equal(tt.wantErr, err != nil)
-		s.Equal(tt.want, got)
-	}
+	})
+	assert.Nil(err)
+	driver, ok = Get("mock")
+	assert.Equal(&mockDriver{
+		BaseDir: baseDir,
+	}, driver)
+	assert.Equal(true, ok)
+	assert.Nil(os.Remove(baseDir))
 }
diff --git a/client/daemon/peer/peertask_file_test.go b/client/daemon/peer/peertask_file_test.go
index 054b22a5..09a74c50 100644
--- a/client/daemon/peer/peertask_file_test.go
+++ b/client/daemon/peer/peertask_file_test.go
@@ -26,14 +26,14 @@ import (
 	"testing"
 	"time"
 
-	"github.com/golang/mock/gomock"
-	testifyassert "github.com/stretchr/testify/assert"
-
 	"d7y.io/dragonfly/v2/client/clientutil"
 	"d7y.io/dragonfly/v2/client/config"
 	"d7y.io/dragonfly/v2/client/daemon/test"
-	"d7y.io/dragonfly/v2/client/daemon/test/mock/source"
 	"d7y.io/dragonfly/v2/pkg/rpc/scheduler"
+	"d7y.io/dragonfly/v2/pkg/source"
+	sourceMock "d7y.io/dragonfly/v2/pkg/source/mock"
+	"github.com/golang/mock/gomock"
+	testifyassert "github.com/stretchr/testify/assert"
 )
 
 func TestFilePeerTask_BackSource_WithContentLength(t *testing.T) {
@@ -70,13 +70,15 @@ func TestFilePeerTask_BackSource_WithContentLength(t *testing.T) {
 		return rc, rc, nil
 	})
 
-	sourceClient := source.NewMockResourceClient(ctrl)
-	sourceClient.EXPECT().GetContentLength(url, map[string]string{}).DoAndReturn(
-		func(url string, headers map[string]string) (int64, error) {
+	sourceClient := sourceMock.NewMockResourceClient(ctrl)
+	source.Register("http", sourceClient)
+	defer source.UnRegister("http")
+	sourceClient.EXPECT().GetContentLength(gomock.Any(), url, gomock.Any()).DoAndReturn(
+		func(ctx context.Context, url string, headers source.Header) (int64, error) {
 			return int64(len(testBytes)), nil
 		})
-	sourceClient.EXPECT().Download(url, map[string]string{}).DoAndReturn(
-		func(url string, headers map[string]string) (io.ReadCloser, map[string]string, error) {
+	sourceClient.EXPECT().Download(gomock.Any(), url, gomock.Any()).DoAndReturn(
+		func(ctx context.Context, url string, headers source.Header) (io.ReadCloser, map[string]string, error) {
 			return ioutil.NopCloser(bytes.NewBuffer(testBytes)), nil, nil
 		})
 
@@ -88,7 +90,6 @@ func TestFilePeerTask_BackSource_WithContentLength(t *testing.T) {
 		pieceManager: &pieceManager{
 			storageManager:   storageManager,
 			pieceDownloader:  downloader,
-			resourceClient:   sourceClient,
 			computePieceSize: computePieceSize,
 		},
 		storageManager:  storageManager,
@@ -180,14 +181,16 @@ func TestFilePeerTask_BackSource_WithoutContentLength(t *testing.T) {
 			return rc, rc, nil
 		})
 
-	sourceClient := source.NewMockResourceClient(ctrl)
-	sourceClient.EXPECT().GetContentLength(url, map[string]string{}).DoAndReturn(
-		func(url string, headers map[string]string) (int64, error) {
+	sourceClient := sourceMock.NewMockResourceClient(ctrl)
+	source.Register("http", sourceClient)
+	defer source.UnRegister("http")
+	sourceClient.EXPECT().GetContentLength(gomock.Any(), url, source.Header{}).DoAndReturn(
+		func(ctx context.Context, url string, header source.Header) (int64, error) {
 			return -1, nil
 		})
-	sourceClient.EXPECT().Download(url, map[string]string{}).DoAndReturn(
-		func(url string, headers map[string]string) (io.ReadCloser, map[string]string, error) {
-			return ioutil.NopCloser(bytes.NewBuffer(testBytes)), nil, nil
+	sourceClient.EXPECT().Download(gomock.Any(), url, source.Header{}).DoAndReturn(
+		func(ctx context.Context, url string, header source.Header) (io.ReadCloser, error) {
+			return ioutil.NopCloser(bytes.NewBuffer(testBytes)), nil
 		})
 
 	ptm := &peerTaskManager{
@@ -198,7 +201,6 @@ func TestFilePeerTask_BackSource_WithoutContentLength(t *testing.T) {
 		pieceManager: &pieceManager{
 			storageManager:   storageManager,
 			pieceDownloader:  downloader,
-			resourceClient:   sourceClient,
 			computePieceSize: computePieceSize,
 		},
 		storageManager:  storageManager,
diff --git a/client/daemon/peer/peertask_stream_test.go b/client/daemon/peer/peertask_stream_test.go
index 68b69b69..59e930c4 100644
--- a/client/daemon/peer/peertask_stream_test.go
+++ b/client/daemon/peer/peertask_stream_test.go
@@ -25,13 +25,14 @@ import (
 	"testing"
 	"time"
 
+	"d7y.io/dragonfly/v2/pkg/source"
+	sourceMock "d7y.io/dragonfly/v2/pkg/source/mock"
 	"github.com/golang/mock/gomock"
 	testifyassert "github.com/stretchr/testify/assert"
 
 	"d7y.io/dragonfly/v2/client/clientutil"
 	"d7y.io/dragonfly/v2/client/config"
 	"d7y.io/dragonfly/v2/client/daemon/test"
-	"d7y.io/dragonfly/v2/client/daemon/test/mock/source"
 	"d7y.io/dragonfly/v2/pkg/rpc/scheduler"
 )
 
@@ -67,14 +68,16 @@ func TestStreamPeerTask_BackSource_WithContentLength(t *testing.T) {
 			return rc, rc, nil
 		})
 
-	sourceClient := source.NewMockResourceClient(ctrl)
-	sourceClient.EXPECT().GetContentLength(url, map[string]string{}).DoAndReturn(
-		func(url string, headers map[string]string) (int64, error) {
+	sourceClient := sourceMock.NewMockResourceClient(ctrl)
+	source.Register("http", sourceClient)
+	defer source.UnRegister("http")
+	sourceClient.EXPECT().GetContentLength(gomock.Any(), url, source.Header{}).DoAndReturn(
+		func(ctx context.Context, url string, headers source.Header) (int64, error) {
 			return int64(len(testBytes)), nil
 		})
-	sourceClient.EXPECT().Download(url, map[string]string{}).DoAndReturn(
-		func(url string, headers map[string]string) (io.ReadCloser, map[string]string, error) {
-			return ioutil.NopCloser(bytes.NewBuffer(testBytes)), nil, nil
+	sourceClient.EXPECT().Download(gomock.Any(), url, source.Header{}).DoAndReturn(
+		func(ctx context.Context, url string, headers source.Header) (io.ReadCloser, error) {
+			return ioutil.NopCloser(bytes.NewBuffer(testBytes)), nil
 		})
 
 	ptm := &peerTaskManager{
@@ -85,7 +88,6 @@ func TestStreamPeerTask_BackSource_WithContentLength(t *testing.T) {
 		pieceManager: &pieceManager{
 			storageManager:   storageManager,
 			pieceDownloader:  downloader,
-			resourceClient:   sourceClient,
 			computePieceSize: computePieceSize,
 		},
 		storageManager:  storageManager,
@@ -108,7 +110,6 @@ func TestStreamPeerTask_BackSource_WithContentLength(t *testing.T) {
 		&pieceManager{
 			storageManager:  storageManager,
 			pieceDownloader: downloader,
-			resourceClient:  sourceClient,
 			computePieceSize: func(contentLength int64) int32 {
 				return int32(pieceSize)
 			},
@@ -166,13 +167,15 @@ func TestStreamPeerTask_BackSource_WithoutContentLength(t *testing.T) {
 			return rc, rc, nil
 		})
 
-	sourceClient := source.NewMockResourceClient(ctrl)
-	sourceClient.EXPECT().GetContentLength(url, map[string]string{}).DoAndReturn(
-		func(url string, headers map[string]string) (int64, error) {
+	sourceClient := sourceMock.NewMockResourceClient(ctrl)
+	source.Register("http", sourceClient)
+	defer source.UnRegister("http")
+	sourceClient.EXPECT().GetContentLength(gomock.Any(), url, source.Header{}).DoAndReturn(
+		func(ctx context.Context, url string, headers source.Header) (int64, error) {
 			return -1, nil
 		})
-	sourceClient.EXPECT().Download(url, map[string]string{}).DoAndReturn(
-		func(url string, headers map[string]string) (io.ReadCloser, map[string]string, error) {
+	sourceClient.EXPECT().Download(gomock.Any(), url, source.Header{}).DoAndReturn(
+		func(ctx context.Context, url string, headers source.Header) (io.ReadCloser, map[string]string, error) {
 			return ioutil.NopCloser(bytes.NewBuffer(testBytes)), nil, nil
 		})
 
@@ -184,7 +187,6 @@ func TestStreamPeerTask_BackSource_WithoutContentLength(t *testing.T) {
 		pieceManager: &pieceManager{
 			storageManager:   storageManager,
 			pieceDownloader:  downloader,
-			resourceClient:   sourceClient,
 			computePieceSize: computePieceSize,
 		},
 		storageManager:  storageManager,
@@ -207,7 +209,6 @@ func TestStreamPeerTask_BackSource_WithoutContentLength(t *testing.T) {
 		&pieceManager{
 			storageManager:  storageManager,
 			pieceDownloader: downloader,
-			resourceClient:  sourceClient,
 			computePieceSize: func(contentLength int64) int32 {
 				return int32(pieceSize)
 			},
diff --git a/client/daemon/peer/piece_manager_test.go b/client/daemon/peer/piece_manager_test.go
index beb2878f..bae9a58f 100644
--- a/client/daemon/peer/piece_manager_test.go
+++ b/client/daemon/peer/piece_manager_test.go
@@ -30,6 +30,7 @@ import (
 	"testing"
 	"time"
 
+	"d7y.io/dragonfly/v2/pkg/source"
 	"github.com/golang/mock/gomock"
 	testifyassert "github.com/stretchr/testify/assert"
 
@@ -41,12 +42,13 @@ import (
 	"d7y.io/dragonfly/v2/pkg/rpc/base"
 	_ "d7y.io/dragonfly/v2/pkg/rpc/dfdaemon/server"
 	"d7y.io/dragonfly/v2/pkg/rpc/scheduler"
+	"d7y.io/dragonfly/v2/pkg/source/httpprotocol"
 )
 
 func TestPieceManager_DownloadSource(t *testing.T) {
 	assert := testifyassert.New(t)
 	ctrl := gomock.NewController(t)
-
+	source.Register("http", httpprotocol.NewHTTPSourceClient())
 	testBytes, err := ioutil.ReadFile(test.File)
 	assert.Nil(err, "load test file")
 
diff --git a/client/daemon/test/mock/source/source_client.go b/client/daemon/test/mock/source/source_client.go
deleted file mode 100644
index 48f17a00..00000000
--- a/client/daemon/test/mock/source/source_client.go
+++ /dev/null
@@ -1,96 +0,0 @@
-// Code generated by MockGen. DO NOT EDIT.
-// Source: ../../../../../cdnsystem/source/source_client.go
-
-// Package source is a generated GoMock package.
-package source
-
-import (
-	io "io"
-	reflect "reflect"
-
-	gomock "github.com/golang/mock/gomock"
-)
-
-// MockResourceClient is a mock of ResourceClient interface.
-type MockResourceClient struct {
-	ctrl     *gomock.Controller
-	recorder *MockResourceClientMockRecorder
-}
-
-// MockResourceClientMockRecorder is the mock recorder for MockResourceClient.
-type MockResourceClientMockRecorder struct {
-	mock *MockResourceClient
-}
-
-// NewMockResourceClient creates a new mock instance.
-func NewMockResourceClient(ctrl *gomock.Controller) *MockResourceClient {
-	mock := &MockResourceClient{ctrl: ctrl}
-	mock.recorder = &MockResourceClientMockRecorder{mock}
-	return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use.
-func (m *MockResourceClient) EXPECT() *MockResourceClientMockRecorder {
-	return m.recorder
-}
-
-// Download mocks base method.
-func (m *MockResourceClient) Download(url string, headers map[string]string) (io.ReadCloser, map[string]string, error) {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "Download", url, headers)
-	ret0, _ := ret[0].(io.ReadCloser)
-	ret1, _ := ret[1].(map[string]string)
-	ret2, _ := ret[2].(error)
-	return ret0, ret1, ret2
-}
-
-// Download indicates an expected call of Download.
-func (mr *MockResourceClientMockRecorder) Download(url, headers interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Download", reflect.TypeOf((*MockResourceClient)(nil).Download), url, headers)
-}
-
-// GetContentLength mocks base method.
-func (m *MockResourceClient) GetContentLength(url string, headers map[string]string) (int64, error) {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "GetContentLength", url, headers)
-	ret0, _ := ret[0].(int64)
-	ret1, _ := ret[1].(error)
-	return ret0, ret1
-}
-
-// GetContentLength indicates an expected call of GetContentLength.
-func (mr *MockResourceClientMockRecorder) GetContentLength(url, headers interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetContentLength", reflect.TypeOf((*MockResourceClient)(nil).GetContentLength), url, headers)
-}
-
-// IsExpired mocks base method.
-func (m *MockResourceClient) IsExpired(url string, headers, expireInfo map[string]string) (bool, error) {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "IsExpired", url, headers, expireInfo)
-	ret0, _ := ret[0].(bool)
-	ret1, _ := ret[1].(error)
-	return ret0, ret1
-}
-
-// IsExpired indicates an expected call of IsExpired.
-func (mr *MockResourceClientMockRecorder) IsExpired(url, headers, expireInfo interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsExpired", reflect.TypeOf((*MockResourceClient)(nil).IsExpired), url, headers, expireInfo)
-}
-
-// IsSupportRange mocks base method.
-func (m *MockResourceClient) IsSupportRange(url string, headers map[string]string) (bool, error) {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "IsSupportRange", url, headers)
-	ret0, _ := ret[0].(bool)
-	ret1, _ := ret[1].(error)
-	return ret0, ret1
-}
-
-// IsSupportRange indicates an expected call of IsSupportRange.
-func (mr *MockResourceClientMockRecorder) IsSupportRange(url, headers interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsSupportRange", reflect.TypeOf((*MockResourceClient)(nil).IsSupportRange), url, headers)
-}
diff --git a/pkg/source/httpprotocol/http_source_client_test.go b/pkg/source/httpprotocol/http_source_client_test.go
new file mode 100644
index 00000000..efcfefef
--- /dev/null
+++ b/pkg/source/httpprotocol/http_source_client_test.go
@@ -0,0 +1,324 @@
+/*
+ *     Copyright 2020 The Dragonfly Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package httpprotocol
+
+import (
+	"context"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"net/url"
+	"testing"
+	"time"
+
+	"d7y.io/dragonfly/v2/pkg/source"
+	"d7y.io/dragonfly/v2/pkg/util/rangeutils"
+	"github.com/go-http-utils/headers"
+	"github.com/jarcoal/httpmock"
+	"github.com/stretchr/testify/suite"
+)
+
+func TestHTTPSourceClientTestSuite(t *testing.T) {
+	suite.Run(t, new(HTTPSourceClientTestSuite))
+}
+
+type HTTPSourceClientTestSuite struct {
+	suite.Suite
+	source.ResourceClient
+}
+
+func (suite *HTTPSourceClientTestSuite) SetupSuite() {
+	suite.ResourceClient = NewHTTPSourceClient()
+	httpmock.ActivateNonDefault(defaultHTTPClient)
+}
+
+func (suite *HTTPSourceClientTestSuite) TearDownSuite() {
+	httpmock.DeactivateAndReset()
+}
+
+var (
+	timeoutURL               = "http://timeout.com"
+	normalURL                = "http://normal.com"
+	errorURL                 = "http://error.com"
+	forbiddenURL             = "http://forbidden.com"
+	notfoundURL              = "http://notfound.com"
+	normalNotSupportRangeURL = "http://notsuppertrange.com"
+)
+
+var (
+	testContent  = "l am test case"
+	lastModified = "Sun, 06 Jun 2021 12:52:30 GMT"
+	//etag         = "UMiJT4h7MCEAEgnqCLA2CdAaABnK" // todo etag business code can not obtain
+	etag = ""
+)
+
+func (suite *HTTPSourceClientTestSuite) SetupTest() {
+	httpmock.Reset()
+	httpmock.RegisterResponder(http.MethodGet, timeoutURL, func(request *http.Request) (*http.Response, error) {
+		// To simulate the timeout
+		time.Sleep(5 * time.Second)
+		return httpmock.NewStringResponse(http.StatusOK, "ok"), nil
+	})
+
+	httpmock.RegisterResponder(http.MethodGet, normalURL, func(request *http.Request) (*http.Response, error) {
+		if rang := request.Header.Get(headers.Range); rang != "" {
+			r, _ := rangeutils.ParseRange(rang[6:])
+			res := &http.Response{
+				StatusCode:    http.StatusPartialContent,
+				ContentLength: int64(r.EndIndex) - int64(r.StartIndex) + int64(1),
+				Body:          httpmock.NewRespBodyFromString(testContent[r.StartIndex:r.EndIndex]),
+				Header: http.Header{
+					headers.LastModified: []string{lastModified},
+					headers.ETag:         []string{etag},
+				},
+			}
+			return res, nil
+		}
+		if expire := request.Header.Get(headers.IfModifiedSince); expire != "" {
+			res := &http.Response{
+				StatusCode:    http.StatusNotModified,
+				ContentLength: int64(len(testContent)),
+				Body:          httpmock.NewRespBodyFromString(testContent),
+				Header: http.Header{
+					headers.LastModified: []string{lastModified},
+					headers.ETag:         []string{etag},
+				},
+			}
+			return res, nil
+		}
+		res := &http.Response{
+			StatusCode:    http.StatusOK,
+			ContentLength: 14,
+			Body:          httpmock.NewRespBodyFromString(testContent),
+			Header: http.Header{
+				headers.LastModified: []string{lastModified},
+				headers.ETag:         []string{etag},
+			},
+		}
+		return res, nil
+	})
+
+	httpmock.RegisterResponder(http.MethodGet, forbiddenURL, httpmock.NewStringResponder(http.StatusForbidden, "forbidden"))
+	httpmock.RegisterResponder(http.MethodGet, notfoundURL, httpmock.NewStringResponder(http.StatusNotFound, "not found"))
+	httpmock.RegisterResponder(http.MethodGet, normalNotSupportRangeURL, httpmock.NewStringResponder(http.StatusOK, testContent))
+	httpmock.RegisterResponder(http.MethodGet, errorURL, httpmock.NewErrorResponder(fmt.Errorf("error")))
+}
+
+func (suite *HTTPSourceClientTestSuite) TestNewHTTPSourceClient() {
+	var sourceClient source.ResourceClient
+	sourceClient = NewHTTPSourceClient()
+	suite.Equal(defaultHTTPClient, sourceClient.(*httpSourceClient).httpClient)
+	suite.EqualValues(*defaultHTTPClient, *sourceClient.(*httpSourceClient).httpClient)
+
+	expectedHTTPClient := &http.Client{}
+	sourceClient = NewHTTPSourceClient(WithHTTPClient(expectedHTTPClient))
+	suite.Equal(expectedHTTPClient, sourceClient.(*httpSourceClient).httpClient)
+	suite.EqualValues(*expectedHTTPClient, *sourceClient.(*httpSourceClient).httpClient)
+}
+
+func (suite *HTTPSourceClientTestSuite) TestHttpSourceClientDownloadWithExpire() {
+	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
+	reader, expireInfo, err := suite.DownloadWithExpire(ctx, timeoutURL, map[string]string{})
+	cancel()
+	suite.NotNil(err)
+	suite.Equal("Get \"http://timeout.com\": context deadline exceeded", err.Error())
+	suite.Nil(reader)
+	suite.Nil(expireInfo)
+
+	type args struct {
+		ctx    context.Context
+		url    string
+		header source.Header
+	}
+	tests := []struct {
+		name       string
+		args       args
+		content    string
+		expireInfo map[string]string
+		wantErr    error
+	}{
+		{
+			name: "normal download",
+			args: args{
+				ctx:    context.Background(),
+				url:    normalURL,
+				header: nil,
+			},
+			content: testContent,
+			expireInfo: map[string]string{
+				headers.LastModified: lastModified,
+				headers.ETag:         etag,
+			},
+			wantErr: nil,
+		}, {
+			name: "range download",
+			args: args{
+				ctx:    context.Background(),
+				url:    normalURL,
+				header: source.Header{"Range": fmt.Sprintf("bytes=%s", "0-3")},
+			},
+			content: testContent[0:3],
+			expireInfo: map[string]string{
+				headers.LastModified: lastModified,
+				headers.ETag:         etag,
+			},
+			wantErr: nil,
+		}, {
+			name: "not found download",
+			args: args{
+				ctx:    context.Background(),
+				url:    notfoundURL,
+				header: nil,
+			},
+			content:    "",
+			expireInfo: nil,
+			wantErr:    fmt.Errorf("unexpected status code: %d", http.StatusNotFound),
+		}, {
+			name: "error download",
+			args: args{
+				ctx:    context.Background(),
+				url:    errorURL,
+				header: nil,
+			},
+			content:    "",
+			expireInfo: nil,
+			wantErr: &url.Error{
+				Op:  "Get",
+				URL: errorURL,
+				Err: fmt.Errorf("error"),
+			},
+		},
+	}
+	for _, tt := range tests {
+		suite.Run(tt.name, func() {
+			reader, expire, err := suite.DownloadWithExpire(tt.args.ctx, tt.args.url, tt.args.header)
+			suite.Equal(tt.wantErr, err)
+			if err != nil {
+				return
+			}
+			bytes, err := ioutil.ReadAll(reader)
+			suite.Nil(err)
+			suite.Equal(tt.content, string(bytes))
+			suite.Equal(tt.expireInfo, expire)
+		})
+	}
+}
+
+func (suite *HTTPSourceClientTestSuite) TestHttpSourceClientGetContentLength() {
+	type args struct {
+		ctx    context.Context
+		url    string
+		header source.Header
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    int64
+		wantErr error
+	}{
+		{name: "support content length", args: args{ctx: context.Background(), url: normalURL, header: map[string]string{}}, want: int64(len(testContent)),
+			wantErr: nil},
+		{name: "not support content length", args: args{ctx: context.Background(), url: normalURL, header: source.Header{"Range": fmt.Sprintf("bytes=%s",
+			"0-3")}}, want: 4,
+			wantErr: nil},
+	}
+	for _, tt := range tests {
+		suite.Run(tt.name, func() {
+			got, err := suite.GetContentLength(tt.args.ctx, tt.args.url, tt.args.header)
+			suite.Equal(tt.wantErr, err)
+			suite.Equal(tt.want, got)
+		})
+	}
+}
+
+func (suite *HTTPSourceClientTestSuite) TestHttpSourceClientIsExpired() {
+	type args struct {
+		ctx        context.Context
+		url        string
+		header     source.Header
+		expireInfo map[string]string
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    bool
+		wantErr bool
+	}{
+		{name: "not expire", args: args{context.Background(), normalURL, source.Header{}, map[string]string{headers.LastModified: lastModified,
+			headers.ETag: etag}}, want: false, wantErr: false},
+		{name: "error not expire", args: args{context.Background(), errorURL, source.Header{}, map[string]string{headers.LastModified: lastModified,
+			headers.ETag: etag}}, want: false, wantErr: true},
+		{name: "expired", args: args{context.Background(), normalURL, source.Header{}, map[string]string{}}, want: true, wantErr: false},
+	}
+	for _, tt := range tests {
+		suite.Run(tt.name, func() {
+			got, err := suite.IsExpired(tt.args.ctx, tt.args.url, tt.args.header, tt.args.expireInfo)
+			suite.Equal(tt.want, got)
+			suite.Equal(tt.wantErr, err != nil)
+		})
+	}
+}
+
+func (suite *HTTPSourceClientTestSuite) TestHttpSourceClientIsSupportRange() {
+	httpmock.RegisterResponder(http.MethodGet, timeoutURL, func(request *http.Request) (*http.Response, error) {
+		time.Sleep(3 * time.Second)
+		return httpmock.NewStringResponse(http.StatusOK, "ok"), nil
+	})
+	parent := context.Background()
+	ctx, cancel := context.WithTimeout(parent, 1*time.Second)
+	support, err := suite.IsSupportRange(ctx, timeoutURL, nil)
+	cancel()
+	suite.NotNil(err)
+	suite.Equal("Get \"http://timeout.com\": context deadline exceeded", err.Error())
+	suite.Equal(false, support)
+	httpmock.RegisterResponder(http.MethodGet, normalURL, httpmock.NewStringResponder(http.StatusPartialContent, ""))
+	httpmock.RegisterResponder(http.MethodGet, "http://notSupportRange.com", httpmock.NewStringResponder(http.StatusOK, ""))
+	httpmock.RegisterResponder(http.MethodGet, "http://error.com", httpmock.NewErrorResponder(fmt.Errorf("xxx")))
+	type args struct {
+		ctx    context.Context
+		url    string
+		header map[string]string
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    bool
+		wantErr bool
+	}{
+		{name: "support", args: args{ctx: context.Background(), url: normalURL, header: source.Header{"Range": fmt.Sprintf("bytes=%s",
+			"0-3")}}, want: true, wantErr: false},
+		{name: "notSupport", args: args{ctx: context.Background(), url: normalNotSupportRangeURL, header: source.Header{"Range": fmt.Sprintf("bytes=%s",
+			"0-3")}}, want: false, wantErr: false},
+	}
+	for _, tt := range tests {
+		suite.Run(tt.name, func() {
+			got, err := suite.IsSupportRange(tt.args.ctx, tt.args.url, tt.args.header)
+			suite.Equal(tt.wantErr, err != nil)
+			suite.Equal(tt.want, got)
+		})
+	}
+}
+
+func (suite *HTTPSourceClientTestSuite) TestHttpSourceClientDoRequest() {
+	var testURL = "http://www.hackhttp.com"
+	httpmock.RegisterResponder(http.MethodGet, testURL, httpmock.NewStringResponder(http.StatusOK, "ok"))
+	res, err := suite.ResourceClient.(*httpSourceClient).doRequest(context.Background(), http.MethodGet, "http://www.hackhttp.com", nil)
+	suite.Nil(err)
+	bytes, err := ioutil.ReadAll(res.Body)
+	suite.Nil(err)
+	suite.EqualValues("ok", string(bytes))
+}
diff --git a/pkg/util/timeutils/time_utils_test.go b/pkg/util/timeutils/time_utils_test.go
index b99a01c4..00541045 100644
--- a/pkg/util/timeutils/time_utils_test.go
+++ b/pkg/util/timeutils/time_utils_test.go
@@ -36,3 +36,57 @@ func TestSinceInMilliseconds(t *testing.T) {
 
 	assert.GreaterOrEqual(t, SinceInMilliseconds(tim), int64(500))
 }
+
+func TestUnixMillis(t *testing.T) {
+	const Layout = "Mon, 02 Jan 2006 15:04:05 GMT"
+	sample, _ := time.Parse(Layout, "Mon, 02 Jan 2006 15:04:05 GMT")
+	type args struct {
+		timeString string
+	}
+	tests := []struct {
+		name string
+		args args
+		want int64
+	}{
+		{
+			name: "convert a string time to an int64 milliseconds",
+			args: args{"Mon, 02 Jan 2006 15:04:05 GMT"},
+			want: sample.Unix() * 1000,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := UnixMillis(tt.args.timeString); got != tt.want {
+				t.Errorf("UnixMillis() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestMillisUnixTime(t *testing.T) {
+	const Layout = "2006-01-02 15:04:05"
+	sample, _ := time.ParseInLocation(Layout, "2021-01-02 12:04:05", time.Local)
+	tests := []struct {
+		name string
+		args int64
+		want time.Time
+	}{
+		{
+			name: "convert an int64 milliseconds to a unix time",
+			args: sample.Unix() * 1000,
+			want: sample.Local(),
+		},
+		{
+			name: "convert now",
+			args: time.Now().UnixNano() / int64(time.Millisecond),
+			want: time.Unix(time.Now().Unix(), (time.Now().UnixNano()-time.Now().Unix()*int64(time.Second))/int64(time.Millisecond)*int64(time.Millisecond)),
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := MillisUnixTime(tt.args); got != tt.want {
+				t.Errorf("MillisUnixTime() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}