Repository: dragonflyoss/Dragonfly2
Pull request #879 (2021-12-06 10:34:21)

refactor: dfpath pkg
<!--- Provide a general summary of your changes in the Title above -->

## Description
- Refactor dfpath pkg.
<!--- 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

<!--- 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.
- [x] I have added tests to cover my changes.
- [x] All new and existing tests passed.


codecov-commenter: # [Codecov](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/879?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) Report
> Merging [#879](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/879?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) (3c49928) into [main](https://codecov.io/gh/dragonflyoss/Dragonfly2/commit/f0f4d6c492512d0d6e20a50ad26d4a37c74beecf?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) (f0f4d6c) will **increase** coverage by `41.39%`.
> The diff coverage is `44.00%`.

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

```diff
@@            Coverage Diff            @@
##           main     #879       +/-   ##
=========================================
+ Coverage      0   41.39%   +41.39%     
=========================================
  Files         0      106      +106     
  Lines         0     8255     +8255     
=========================================
+ Hits          0     3417     +3417     
- Misses        0     4394     +4394     
- Partials      0      444      +444     
```

| Flag | Coverage Δ | |
|---|---|---|
| unittests | `41.39% <44.00%> (?)` | |

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/879?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) | Coverage Δ | |
|---|---|---|
| [client/config/dfget.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/879/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-Y2xpZW50L2NvbmZpZy9kZmdldC5nbw==) | `23.52% <ø> (ø)` | |
| [client/config/peerhost.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/879/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-Y2xpZW50L2NvbmZpZy9wZWVyaG9zdC5nbw==) | `35.29% <ø> (ø)` | |
| [client/daemon/daemon.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/879/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-Y2xpZW50L2RhZW1vbi9kYWVtb24uZ28=) | `5.59% <0.00%> (ø)` | |
| [manager/config/config.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/879/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-bWFuYWdlci9jb25maWcvY29uZmlnLmdv) | `0.00% <ø> (ø)` | |
| [pkg/source/plugin.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/879/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-cGtnL3NvdXJjZS9wbHVnaW4uZ28=) | `0.00% <0.00%> (ø)` | |
| [pkg/source/source\_client.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/879/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-cGtnL3NvdXJjZS9zb3VyY2VfY2xpZW50Lmdv) | `1.98% <0.00%> (ø)` | |
| [scheduler/config/config.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/879/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-c2NoZWR1bGVyL2NvbmZpZy9jb25maWcuZ28=) | `4.81% <ø> (ø)` | |
| [scheduler/core/evaluator/evaluator.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/879/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-c2NoZWR1bGVyL2NvcmUvZXZhbHVhdG9yL2V2YWx1YXRvci5nbw==) | `71.42% <0.00%> (ø)` | |
| [internal/dfpath/dfpath.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/879/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-aW50ZXJuYWwvZGZwYXRoL2RmcGF0aC5nbw==) | `48.38% <48.38%> (ø)` | |
| [client/config/dynconfig.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/879/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-Y2xpZW50L2NvbmZpZy9keW5jb25maWcuZ28=) | `58.06% <66.66%> (ø)` | |
| ... and [109 more](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/879/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) | |


Patch:
diff --git a/cdn/config/config.go b/cdn/config/config.go
index 1ec7582a..969e163f 100644
--- a/cdn/config/config.go
+++ b/cdn/config/config.go
@@ -185,6 +185,9 @@ type BaseProperties struct {
 	// StorageMode disk/hybrid/memory
 	StorageMode string `yaml:"storageMode" mapstructure:"storageMode"`
 
+	// Log directory
+	LogDir string `yaml:"logDir" mapstructure:"logDir"`
+
 	// Manager configuration
 	Manager ManagerConfig `yaml:"manager" mapstructure:"manager"`
 
diff --git a/client/config/dfget.go b/client/config/dfget.go
index 9f4671d2..eb47cdb2 100644
--- a/client/config/dfget.go
+++ b/client/config/dfget.go
@@ -96,6 +96,15 @@ type ClientOption struct {
 	// ShowProgress shows progress bar, it's conflict with `--console`.
 	ShowProgress bool `yaml:"show-progress,omitempty" mapstructure:"show-progress,omitempty"`
 
+	// DaemonSockPath is dfget daemon socket path.
+	DaemonSockPath string `yaml:"daemonSockPath,omitempty" mapstructure:"daemonSockPath,omitempty"`
+
+	// LogDir is log directory of dfget.
+	LogDir string `yaml:"logDir,omitempty" mapstructure:"logDir,omitempty"`
+
+	// WorkHome is working directory of dfget.
+	WorkHome string `yaml:"workHome,omitempty" mapstructure:"workHome,omitempty"`
+
 	RateLimit rate.Limit `yaml:"rateLimit,omitempty" mapstructure:"rateLimit,omitempty"`
 
 	// Config file paths,
diff --git a/client/config/dynconfig.go b/client/config/dynconfig.go
index 88008da4..d01f823e 100644
--- a/client/config/dynconfig.go
+++ b/client/config/dynconfig.go
@@ -22,7 +22,6 @@ import (
 	"time"
 
 	logger "d7y.io/dragonfly/v2/internal/dflog"
-	"d7y.io/dragonfly/v2/internal/dfpath"
 	internaldynconfig "d7y.io/dragonfly/v2/internal/dynconfig"
 	"d7y.io/dragonfly/v2/manager/searcher"
 	"d7y.io/dragonfly/v2/pkg/rpc/manager"
@@ -30,8 +29,8 @@ import (
 )
 
 var (
-	// Dynconfig configure the cache path
-	cachePath = filepath.Join(dfpath.DefaultCacheDir, "daemon_dynconfig")
+	// Daemon cache file name
+	cacheFileName = "daemon_dynconfig"
 
 	// Watch dynconfig interval
 	watchInterval = 10 * time.Second
@@ -73,9 +72,11 @@ type dynconfig struct {
 	*internaldynconfig.Dynconfig
 	observers map[Observer]struct{}
 	done      chan bool
+	cachePath string
 }
 
-func NewDynconfig(rawManagerClient managerclient.Client, hostOption HostOption, expire time.Duration) (Dynconfig, error) {
+func NewDynconfig(rawManagerClient managerclient.Client, cacheDir string, hostOption HostOption, expire time.Duration) (Dynconfig, error) {
+	cachePath := filepath.Join(cacheDir, cacheFileName)
 	client, err := internaldynconfig.New(
 		internaldynconfig.ManagerSourceType,
 		internaldynconfig.WithManagerClient(newManagerClient(rawManagerClient, hostOption)),
@@ -89,6 +90,7 @@ func NewDynconfig(rawManagerClient managerclient.Client, hostOption HostOption,
 	return &dynconfig{
 		observers: map[Observer]struct{}{},
 		done:      make(chan bool),
+		cachePath: cachePath,
 		Dynconfig: client,
 	}, nil
 }
@@ -159,7 +161,7 @@ func (d *dynconfig) watch() {
 
 func (d *dynconfig) Stop() error {
 	close(d.done)
-	if err := os.Remove(cachePath); err != nil {
+	if err := os.Remove(d.cachePath); err != nil {
 		return err
 	}
 
diff --git a/client/config/peerhost.go b/client/config/peerhost.go
index f0cfe4fa..e321697e 100644
--- a/client/config/peerhost.go
+++ b/client/config/peerhost.go
@@ -50,8 +50,9 @@ type DaemonOption struct {
 	AliveTime  clientutil.Duration `mapstructure:"aliveTime" yaml:"aliveTime"`
 	GCInterval clientutil.Duration `mapstructure:"gcInterval" yaml:"gcInterval"`
 
-	DataDir     string `mapstructure:"dataDir" yaml:"dataDir"`
 	WorkHome    string `mapstructure:"workHome" yaml:"workHome"`
+	CacheDir    string `mapstructure:"cacheDir" yaml:"cacheDir"`
+	LogDir      string `mapstructure:"logDir" yaml:"logDir"`
 	KeepStorage bool   `mapstructure:"keepStorage" yaml:"keepStorage"`
 
 	Scheduler    SchedulerOption `mapstructure:"scheduler" yaml:"scheduler"`
diff --git a/client/config/peerhost_darwin.go b/client/config/peerhost_darwin.go
index ef305f78..f5730541 100644
--- a/client/config/peerhost_darwin.go
+++ b/client/config/peerhost_darwin.go
@@ -1,3 +1,4 @@
+//go:build darwin
 // +build darwin
 
 /*
@@ -26,21 +27,11 @@ import (
 
 	"d7y.io/dragonfly/v2/client/clientutil"
 	"d7y.io/dragonfly/v2/internal/dfnet"
-	"d7y.io/dragonfly/v2/pkg/basic"
 	"d7y.io/dragonfly/v2/pkg/util/hostutils"
 	"d7y.io/dragonfly/v2/pkg/util/net/iputils"
 )
 
-var (
-	PeerHostConfigPath = basic.HomeDir + "/.dragonfly/dfget-daemon.yaml"
-
-	peerHostWorkHome = basic.HomeDir + "/.dragonfly/dfdaemon/"
-	peerHostDataDir  = peerHostWorkHome
-)
-
 var peerHostConfig = DaemonOption{
-	DataDir:     peerHostDataDir,
-	WorkHome:    peerHostWorkHome,
 	AliveTime:   clientutil.Duration{Duration: DefaultDaemonAliveTime},
 	GCInterval:  clientutil.Duration{Duration: DefaultGCInterval},
 	KeepStorage: false,
diff --git a/client/config/peerhost_linux.go b/client/config/peerhost_linux.go
index ff80a599..b5795a5b 100644
--- a/client/config/peerhost_linux.go
+++ b/client/config/peerhost_linux.go
@@ -1,3 +1,4 @@
+//go:build linux
 // +build linux
 
 /*
@@ -26,21 +27,11 @@ import (
 
 	"d7y.io/dragonfly/v2/client/clientutil"
 	"d7y.io/dragonfly/v2/internal/dfnet"
-	"d7y.io/dragonfly/v2/pkg/basic"
 	"d7y.io/dragonfly/v2/pkg/util/hostutils"
 	"d7y.io/dragonfly/v2/pkg/util/net/iputils"
 )
 
-var (
-	PeerHostConfigPath = "/etc/dragonfly/dfget-daemon.yaml"
-
-	peerHostWorkHome = basic.HomeDir + "/.dragonfly/dfget-daemon/"
-	peerHostDataDir  = peerHostWorkHome
-)
-
 var peerHostConfig = DaemonOption{
-	DataDir:     peerHostDataDir,
-	WorkHome:    peerHostWorkHome,
 	AliveTime:   clientutil.Duration{Duration: DefaultDaemonAliveTime},
 	GCInterval:  clientutil.Duration{Duration: DefaultGCInterval},
 	KeepStorage: false,
diff --git a/client/daemon/daemon.go b/client/daemon/daemon.go
index a23f9883..3a2616c2 100644
--- a/client/daemon/daemon.go
+++ b/client/daemon/daemon.go
@@ -85,11 +85,12 @@ type clientDaemon struct {
 	PieceManager    peer.PieceManager
 
 	dynconfig       config.Dynconfig
+	dfpath          dfpath.Dfpath
 	schedulers      []*manager.Scheduler
 	schedulerClient schedulerclient.SchedulerClient
 }
 
-func New(opt *config.DaemonOption) (Daemon, error) {
+func New(opt *config.DaemonOption, d dfpath.Dfpath) (Daemon, error) {
 	host := &scheduler.PeerHost{
 		Uuid:           idgen.UUIDString(),
 		Ip:             opt.Host.AdvertiseIP,
@@ -113,7 +114,7 @@ func New(opt *config.DaemonOption) (Daemon, error) {
 		}
 
 		// New dynconfig client
-		if dynconfig, err = config.NewDynconfig(managerClient, opt.Host, opt.Scheduler.Manager.RefreshInterval); err != nil {
+		if dynconfig, err = config.NewDynconfig(managerClient, d.CacheDir(), opt.Host, opt.Scheduler.Manager.RefreshInterval); err != nil {
 			return nil, err
 		}
 
@@ -137,7 +138,7 @@ func New(opt *config.DaemonOption) (Daemon, error) {
 	}
 
 	// Storage.Option.DataPath is same with Daemon DataDir
-	opt.Storage.DataPath = opt.DataDir
+	opt.Storage.DataPath = d.WorkHome()
 	storageManager, err := storage.NewStorageManager(opt.Storage.StoreStrategy, &opt.Storage,
 		/* gc callback */
 		func(request storage.CommonTaskRequest) {
@@ -217,6 +218,7 @@ func New(opt *config.DaemonOption) (Daemon, error) {
 		StorageManager:  storageManager,
 		GCManager:       gc.NewManager(opt.GCInterval.Duration),
 		dynconfig:       dynconfig,
+		dfpath:          d,
 		schedulers:      schedulers,
 		schedulerClient: sched,
 	}, nil
@@ -320,7 +322,7 @@ func (*clientDaemon) prepareTCPListener(opt config.ListenOption, withTLS bool) (
 func (cd *clientDaemon) Serve() error {
 	cd.GCManager.Start()
 	// TODO remove this field, and use directly dfpath.DaemonSockPath
-	cd.Option.Download.DownloadGRPC.UnixListen.Socket = dfpath.DaemonSockPath
+	cd.Option.Download.DownloadGRPC.UnixListen.Socket = cd.dfpath.DaemonSockPath()
 	// prepare download service listen
 	if cd.Option.Download.DownloadGRPC.UnixListen == nil {
 		return errors.New("download grpc unix listen option is empty")
diff --git a/cmd/cdn/cmd/root.go b/cmd/cdn/cmd/root.go
index 86e80d1c..ec8eaac4 100644
--- a/cmd/cdn/cmd/root.go
+++ b/cmd/cdn/cmd/root.go
@@ -28,6 +28,7 @@ import (
 	"d7y.io/dragonfly/v2/cmd/dependency"
 	logger "d7y.io/dragonfly/v2/internal/dflog"
 	"d7y.io/dragonfly/v2/internal/dflog/logcore"
+	"d7y.io/dragonfly/v2/internal/dfpath"
 	"d7y.io/dragonfly/v2/version"
 )
 
@@ -46,7 +47,14 @@ from remote source repeatedly.`,
 	DisableAutoGenTag: true,
 	SilenceUsage:      true,
 	RunE: func(cmd *cobra.Command, args []string) error {
-		if err := logcore.InitCdnSystem(cfg.Console); err != nil {
+		// Initialize dfpath
+		d, err := initDfpath(cfg.BaseProperties)
+		if err != nil {
+			return err
+		}
+
+		// Initialize logger
+		if err := logcore.InitCdnSystem(cfg.Console, d.LogDir()); err != nil {
 			return errors.Wrap(err, "init cdn system logger")
 		}
 
@@ -70,6 +78,15 @@ func init() {
 	dependency.InitCobra(rootCmd, true, cfg)
 }
 
+func initDfpath(cfg *config.BaseProperties) (dfpath.Dfpath, error) {
+	options := []dfpath.Option{}
+	if cfg.LogDir != "" {
+		options = append(options, dfpath.WithLogDir(cfg.LogDir))
+	}
+
+	return dfpath.New(options...)
+}
+
 func runCdnSystem() error {
 	logger.Infof("Version:\n%s", version.Version())
 	// cdn system config values
diff --git a/cmd/dependency/plugin_cmd.go b/cmd/dependency/plugin_cmd.go
index 11668903..4b2cc67b 100644
--- a/cmd/dependency/plugin_cmd.go
+++ b/cmd/dependency/plugin_cmd.go
@@ -41,14 +41,20 @@ var PluginCmd = &cobra.Command{
 }
 
 func ListAvailablePlugins() {
-	fmt.Fprintf(os.Stderr, "search plugin in %s\n", dfpath.PluginsDir)
-	files, err := ioutil.ReadDir(dfpath.PluginsDir)
+	d, err := dfpath.New()
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "failed to get plugin path: %q\n", err)
+		return
+	}
+
+	fmt.Fprintf(os.Stderr, "search plugin in %s\n", d.PluginDir())
+	files, err := ioutil.ReadDir(d.PluginDir())
 	if os.IsNotExist(err) {
 		fmt.Fprintf(os.Stderr, "no plugin found\n")
 		return
 	}
 	if err != nil {
-		fmt.Fprintf(os.Stderr, "read plugin dir %s error: %s\n", dfpath.PluginsDir, err)
+		fmt.Fprintf(os.Stderr, "read plugin dir %s error: %s\n", d.PluginDir(), err)
 		return
 	}
 	for _, file := range files {
@@ -66,7 +72,7 @@ func ListAvailablePlugins() {
 		typ, name := subs[1], subs[2]
 		switch typ {
 		case string(dfplugin.PluginTypeResource), string(dfplugin.PluginTypeScheduler), string(dfplugin.PluginTypeManager):
-			_, data, err := dfplugin.Load(dfplugin.PluginType(typ), name, map[string]string{})
+			_, data, err := dfplugin.Load(d.PluginDir(), dfplugin.PluginType(typ), name, map[string]string{})
 			if err != nil {
 				fmt.Fprintf(os.Stderr, "not valid plugin binary format %s: %q\n", fileName, err)
 				continue
diff --git a/cmd/dfget/cmd/daemon.go b/cmd/dfget/cmd/daemon.go
index df86129b..59d4c205 100644
--- a/cmd/dfget/cmd/daemon.go
+++ b/cmd/dfget/cmd/daemon.go
@@ -54,7 +54,14 @@ it supports container engine, wget and other downloading tools through proxy fun
 	SilenceUsage:       true,
 	FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true},
 	RunE: func(cmd *cobra.Command, args []string) error {
-		if err := logcore.InitDaemon(cfg.Console); err != nil {
+		// Initialize daemon dfpath
+		d, err := initDaemonDfpath(cfg)
+		if err != nil {
+			return err
+		}
+
+		// Initialize logger
+		if err := logcore.InitDaemon(cfg.Console, d.LogDir()); err != nil {
 			return errors.Wrap(err, "init client daemon logger")
 		}
 
@@ -68,7 +75,7 @@ it supports container engine, wget and other downloading tools through proxy fun
 			return err
 		}
 
-		return runDaemon()
+		return runDaemon(d)
 	},
 }
 
@@ -89,10 +96,27 @@ func init() {
 	}
 }
 
-func runDaemon() error {
+func initDaemonDfpath(cfg *config.DaemonOption) (dfpath.Dfpath, error) {
+	options := []dfpath.Option{}
+	if cfg.WorkHome != "" {
+		options = append(options, dfpath.WithWorkHome(cfg.WorkHome))
+	}
+
+	if cfg.CacheDir != "" {
+		options = append(options, dfpath.WithCacheDir(cfg.CacheDir))
+	}
+
+	if cfg.LogDir != "" {
+		options = append(options, dfpath.WithLogDir(cfg.LogDir))
+	}
+
+	return dfpath.New(options...)
+}
+
+func runDaemon(d dfpath.Dfpath) error {
 	logger.Infof("Version:\n%s", version.Version())
 
-	target := dfnet.NetAddr{Type: dfnet.UNIX, Addr: dfpath.DaemonSockPath}
+	target := dfnet.NetAddr{Type: dfnet.UNIX, Addr: d.DaemonSockPath()}
 	daemonClient, err := client.GetClientByAddr([]dfnet.NetAddr{target})
 	if err != nil {
 		return err
@@ -107,7 +131,7 @@ func runDaemon() error {
 	// 3. If lock fail, checking whether the daemon has been started. If true, return directly.
 	//    Otherwise, wait 50 ms and execute again from 1
 	// 4. Checking timeout about 5s
-	lock := flock.New(dfpath.DaemonLockPath)
+	lock := flock.New(d.DaemonLockPath())
 	timeout := time.After(5 * time.Second)
 	first := time.After(1 * time.Millisecond)
 	tick := time.NewTicker(50 * time.Millisecond)
@@ -146,7 +170,7 @@ func runDaemon() error {
 	ff := dependency.InitMonitor(cfg.Verbose, cfg.PProfPort, cfg.Telemetry)
 	defer ff()
 
-	svr, err := server.New(cfg)
+	svr, err := server.New(cfg, d)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/dfget/cmd/root.go b/cmd/dfget/cmd/root.go
index 9ff8ac0e..3361789d 100644
--- a/cmd/dfget/cmd/root.go
+++ b/cmd/dfget/cmd/root.go
@@ -68,7 +68,14 @@ var rootCmd = &cobra.Command{
 	RunE: func(cmd *cobra.Command, args []string) error {
 		start := time.Now()
 
-		if err := logcore.InitDfget(dfgetConfig.Console); err != nil {
+		// Initialize daemon dfpath
+		d, err := initDfgetDfpath(dfgetConfig)
+		if err != nil {
+			return err
+		}
+
+		// Initialize logger
+		if err := logcore.InitDfget(dfgetConfig.Console, d.LogDir()); err != nil {
 			return errors.Wrap(err, "init client dfget logger")
 		}
 
@@ -89,7 +96,7 @@ var rootCmd = &cobra.Command{
 
 		//  do get file
 		var errInfo string
-		err := runDfget()
+		err = runDfget(d.DfgetLockPath(), dfgetConfig.DaemonSockPath)
 		if err != nil {
 			errInfo = fmt.Sprintf("error: %v", err)
 		}
@@ -152,14 +159,33 @@ func init() {
 
 	flagSet.String("callsystem", dfgetConfig.CallSystem, "The caller name which is mainly used for statistics and access control")
 
+	flagSet.String("workhome", dfgetConfig.WorkHome, "Dfget working directory")
+
+	flagSet.String("log-dir", dfgetConfig.LogDir, "Dfget log directory")
+
+	flagSet.String("daemon-sock-path", dfgetConfig.DaemonSockPath, "The socket path of dfget daemon")
+
 	// Bind cmd flags
 	if err := viper.BindPFlags(flagSet); err != nil {
 		panic(errors.Wrap(err, "bind dfget flags to viper"))
 	}
 }
 
+func initDfgetDfpath(cfg *config.ClientOption) (dfpath.Dfpath, error) {
+	options := []dfpath.Option{}
+	if cfg.WorkHome != "" {
+		options = append(options, dfpath.WithWorkHome(cfg.WorkHome))
+	}
+
+	if cfg.LogDir != "" {
+		options = append(options, dfpath.WithLogDir(cfg.LogDir))
+	}
+
+	return dfpath.New(options...)
+}
+
 // runDfget does some init operations and starts to download.
-func runDfget() error {
+func runDfget(dfgetLockPath, daemonSockPath string) error {
 	logger.Infof("Version:\n%s", version.Version())
 
 	// Dfget config values
@@ -176,7 +202,7 @@ func runDfget() error {
 
 	if dfgetConfig.Pattern != constants.SourcePattern {
 		logger.Info("start to check and spawn daemon")
-		if daemonClient, err = checkAndSpawnDaemon(); err != nil {
+		if daemonClient, err = checkAndSpawnDaemon(dfgetLockPath, daemonSockPath); err != nil {
 			logger.Errorf("check and spawn daemon error: %v", err)
 		} else {
 			logger.Info("check and spawn daemon success")
@@ -187,8 +213,8 @@ func runDfget() error {
 }
 
 // checkAndSpawnDaemon do checking at three checkpoints
-func checkAndSpawnDaemon() (client.DaemonClient, error) {
-	target := dfnet.NetAddr{Type: dfnet.UNIX, Addr: dfpath.DaemonSockPath}
+func checkAndSpawnDaemon(dfgetLockPath, daemonSockPath string) (client.DaemonClient, error) {
+	target := dfnet.NetAddr{Type: dfnet.UNIX, Addr: daemonSockPath}
 	daemonClient, err := client.GetClientByAddr([]dfnet.NetAddr{target})
 	if err != nil {
 		return nil, err
@@ -199,7 +225,7 @@ func checkAndSpawnDaemon() (client.DaemonClient, error) {
 		return daemonClient, nil
 	}
 
-	lock := flock.New(dfpath.DfgetLockPath)
+	lock := flock.New(dfgetLockPath)
 	if err := lock.Lock(); err != nil {
 		return nil, err
 	}
diff --git a/cmd/manager/cmd/root.go b/cmd/manager/cmd/root.go
index 3d0f537a..5914be57 100644
--- a/cmd/manager/cmd/root.go
+++ b/cmd/manager/cmd/root.go
@@ -26,6 +26,7 @@ import (
 	"d7y.io/dragonfly/v2/cmd/dependency"
 	logger "d7y.io/dragonfly/v2/internal/dflog"
 	"d7y.io/dragonfly/v2/internal/dflog/logcore"
+	"d7y.io/dragonfly/v2/internal/dfpath"
 	"d7y.io/dragonfly/v2/manager"
 	"d7y.io/dragonfly/v2/manager/config"
 	"d7y.io/dragonfly/v2/version"
@@ -45,7 +46,14 @@ for managing schedulers and cdns, offering http apis and portal, etc.`,
 	DisableAutoGenTag: true,
 	SilenceUsage:      true,
 	RunE: func(cmd *cobra.Command, args []string) error {
-		if err := logcore.InitManager(cfg.Console); err != nil {
+		// Initialize dfpath
+		d, err := initDfpath(cfg.Server)
+		if err != nil {
+			return err
+		}
+
+		// Initialize logger
+		if err := logcore.InitManager(cfg.Console, d.LogDir()); err != nil {
 			return errors.Wrap(err, "init manager logger")
 		}
 
@@ -54,7 +62,7 @@ for managing schedulers and cdns, offering http apis and portal, etc.`,
 			return err
 		}
 
-		return runManager()
+		return runManager(d)
 	},
 }
 
@@ -75,7 +83,16 @@ func init() {
 	dependency.InitCobra(rootCmd, true, cfg)
 }
 
-func runManager() error {
+func initDfpath(cfg *config.ServerConfig) (dfpath.Dfpath, error) {
+	options := []dfpath.Option{}
+	if cfg.LogDir != "" {
+		options = append(options, dfpath.WithLogDir(cfg.LogDir))
+	}
+
+	return dfpath.New(options...)
+}
+
+func runManager(d dfpath.Dfpath) error {
 	logger.Infof("Version:\n%s", version.Version())
 	// manager config values
 	s, err := yaml.Marshal(cfg)
@@ -89,7 +106,7 @@ func runManager() error {
 	ff := dependency.InitMonitor(cfg.Verbose, cfg.PProfPort, cfg.Telemetry)
 	defer ff()
 
-	svr, err := manager.New(cfg)
+	svr, err := manager.New(cfg, d)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/scheduler/cmd/root.go b/cmd/scheduler/cmd/root.go
index 980b3549..a3a80d92 100644
--- a/cmd/scheduler/cmd/root.go
+++ b/cmd/scheduler/cmd/root.go
@@ -26,6 +26,7 @@ import (
 	"d7y.io/dragonfly/v2/cmd/dependency"
 	logger "d7y.io/dragonfly/v2/internal/dflog"
 	"d7y.io/dragonfly/v2/internal/dflog/logcore"
+	"d7y.io/dragonfly/v2/internal/dfpath"
 	"d7y.io/dragonfly/v2/scheduler"
 	"d7y.io/dragonfly/v2/scheduler/config"
 	"d7y.io/dragonfly/v2/version"
@@ -45,8 +46,14 @@ generate and maintain a P2P network during the download process, and push suitab
 	DisableAutoGenTag: true,
 	SilenceUsage:      true,
 	RunE: func(cmd *cobra.Command, args []string) error {
+		// Initialize dfpath
+		d, err := initDfpath(cfg.Server)
+		if err != nil {
+			return err
+		}
+
 		// Initialize logger
-		if err := logcore.InitScheduler(cfg.Console); err != nil {
+		if err := logcore.InitScheduler(cfg.Console, d.LogDir()); err != nil {
 			return errors.Wrap(err, "init scheduler logger")
 		}
 
@@ -60,7 +67,7 @@ generate and maintain a P2P network during the download process, and push suitab
 			return err
 		}
 
-		return runScheduler()
+		return runScheduler(d)
 	},
 }
 
@@ -80,7 +87,20 @@ func init() {
 	dependency.InitCobra(rootCmd, true, cfg)
 }
 
-func runScheduler() error {
+func initDfpath(cfg *config.ServerConfig) (dfpath.Dfpath, error) {
+	options := []dfpath.Option{}
+	if cfg.LogDir != "" {
+		options = append(options, dfpath.WithLogDir(cfg.LogDir))
+	}
+
+	if cfg.CacheDir != "" {
+		options = append(options, dfpath.WithCacheDir(cfg.CacheDir))
+	}
+
+	return dfpath.New(options...)
+}
+
+func runScheduler(d dfpath.Dfpath) error {
 	logger.Infof("Version:\n%s", version.Version())
 
 	// scheduler config values
@@ -91,7 +111,7 @@ func runScheduler() error {
 	ff := dependency.InitMonitor(cfg.Verbose, cfg.PProfPort, cfg.Telemetry)
 	defer ff()
 
-	svr, err := scheduler.New(cfg)
+	svr, err := scheduler.New(cfg, d)
 	if err != nil {
 		return err
 	}
diff --git a/go.sum b/go.sum
index 49516486..a09edb28 100644
--- a/go.sum
+++ b/go.sum
@@ -261,7 +261,6 @@ github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gG
 github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
-github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
 github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
 github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
diff --git a/internal/dflog/logcore/init.go b/internal/dflog/logcore/init.go
index a13b57c1..d4fd49a0 100644
--- a/internal/dflog/logcore/init.go
+++ b/internal/dflog/logcore/init.go
@@ -18,17 +18,17 @@ package logcore
 
 import (
 	"path"
+	"path/filepath"
 
 	logger "d7y.io/dragonfly/v2/internal/dflog"
-	"d7y.io/dragonfly/v2/internal/dfpath"
 )
 
-func InitManager(console bool) error {
+func InitManager(console bool, dir string) error {
 	if console {
 		return nil
 	}
 
-	logDir := path.Join(dfpath.LogDir, "manager")
+	logDir := filepath.Join(dir, "manager")
 
 	coreLogger, err := CreateLogger(path.Join(logDir, CoreLogFileName), false, false)
 	if err != nil {
@@ -57,12 +57,12 @@ func InitManager(console bool) error {
 	return nil
 }
 
-func InitScheduler(console bool) error {
+func InitScheduler(console bool, dir string) error {
 	if console {
 		return nil
 	}
 
-	logDir := path.Join(dfpath.LogDir, "scheduler")
+	logDir := filepath.Join(dir, "scheduler")
 
 	coreLogger, err := CreateLogger(path.Join(logDir, CoreLogFileName), false, false)
 	if err != nil {
@@ -97,12 +97,12 @@ func InitScheduler(console bool) error {
 	return nil
 }
 
-func InitCdnSystem(console bool) error {
+func InitCdnSystem(console bool, dir string) error {
 	if console {
 		return nil
 	}
 
-	logDir := path.Join(dfpath.LogDir, "cdn")
+	logDir := filepath.Join(dir, "cdn")
 
 	coreLogger, err := CreateLogger(path.Join(logDir, CoreLogFileName), false, false)
 	if err != nil {
@@ -148,12 +148,12 @@ func InitCdnSystem(console bool) error {
 	return nil
 }
 
-func InitDaemon(console bool) error {
+func InitDaemon(console bool, dir string) error {
 	if console {
 		return nil
 	}
 
-	logDir := path.Join(dfpath.LogDir, "daemon")
+	logDir := filepath.Join(dir, "daemon")
 
 	coreLogger, err := CreateLogger(path.Join(logDir, CoreLogFileName), false, false)
 	if err != nil {
@@ -176,12 +176,12 @@ func InitDaemon(console bool) error {
 	return nil
 }
 
-func InitDfget(console bool) error {
+func InitDfget(console bool, dir string) error {
 	if console {
 		return nil
 	}
 
-	logDir := path.Join(dfpath.LogDir, "dfget")
+	logDir := filepath.Join(dir, "dfget")
 
 	coreLogger, err := CreateLogger(path.Join(logDir, CoreLogFileName), false, false)
 	if err != nil {
diff --git a/internal/dfpath/cache_darwin.go b/internal/dfpath/cache_darwin.go
deleted file mode 100644
index 8dadb202..00000000
--- a/internal/dfpath/cache_darwin.go
+++ /dev/null
@@ -1,23 +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 dfpath
-
-import (
-	"path/filepath"
-)
-
-var DefaultCacheDir = filepath.Join(WorkHome, "cache")
diff --git a/internal/dfpath/config_darwin.go b/internal/dfpath/config_darwin.go
deleted file mode 100644
index 8d07ad1b..00000000
--- a/internal/dfpath/config_darwin.go
+++ /dev/null
@@ -1,23 +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 dfpath
-
-import (
-	"path/filepath"
-)
-
-var DefaultConfigDir = filepath.Join(WorkHome, "config")
diff --git a/internal/dfpath/config_linux.go b/internal/dfpath/config_linux.go
deleted file mode 100644
index 8d09a1bd..00000000
--- a/internal/dfpath/config_linux.go
+++ /dev/null
@@ -1,19 +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 dfpath
-
-var DefaultConfigDir = "/etc/dragonfly"
diff --git a/internal/dfpath/dfpath.go b/internal/dfpath/dfpath.go
index 2303f93a..46209068 100644
--- a/internal/dfpath/dfpath.go
+++ b/internal/dfpath/dfpath.go
@@ -18,36 +18,131 @@ package dfpath
 
 import (
 	"path/filepath"
+	"sync"
 
 	"d7y.io/dragonfly/v2/pkg/util/fileutils"
 )
 
-var (
-	DefaultDataDir = filepath.Join(WorkHome, "data")
-	DaemonSockPath = filepath.Join(WorkHome, "daemon.sock")
-	DaemonLockPath = filepath.Join(WorkHome, "daemon.lock")
-	DfgetLockPath  = filepath.Join(WorkHome, "dfget.lock")
-	PluginsDir     = filepath.Join(WorkHome, "plugins")
-)
+// Dfpath is the interface used for init project path
+type Dfpath interface {
+	WorkHome() string
+	CacheDir() string
+	LogDir() string
+	DataDir() string
+	PluginDir() string
+	DaemonSockPath() string
+	DaemonLockPath() string
+	DfgetLockPath() string
+}
 
-func init() {
-	if err := fileutils.MkdirAll(WorkHome); err != nil {
-		panic(err)
-	}
+// Dfpath provides init project path function
+type dfpath struct {
+	workHome       string
+	cacheDir       string
+	logDir         string
+	dataDir        string
+	pluginDir      string
+	daemonSockPath string
+	daemonLockPath string
+	dfgetLockPath  string
+}
 
-	if err := fileutils.MkdirAll(DefaultConfigDir); err != nil {
-		panic(err)
+// Cache of the dfpath
+var cache struct {
+	sync.Once
+	d   *dfpath
+	err error
+}
+
+// Option is a functional option for configuring the dfpath
+type Option func(d *dfpath)
+
+// WithWorkHome set the workhome directory
+func WithWorkHome(dir string) Option {
+	return func(d *dfpath) {
+		d.workHome = dir
 	}
+}
 
-	if err := fileutils.MkdirAll(DefaultCacheDir); err != nil {
-		panic(err)
+// WithCacheDir set the cache directory
+func WithCacheDir(dir string) Option {
+	return func(d *dfpath) {
+		d.cacheDir = dir
 	}
+}
 
-	if err := fileutils.MkdirAll(LogDir); err != nil {
-		panic(err)
+// WithLogDir set the log directory
+func WithLogDir(dir string) Option {
+	return func(d *dfpath) {
+		d.logDir = dir
 	}
+}
+
+// New returns a new dfpath interface
+func New(options ...Option) (Dfpath, error) {
+	cache.Do(func() {
+		d := &dfpath{
+			workHome: DefaultWorkHome,
+			cacheDir: DefaultCacheDir,
+			logDir:   DefaultLogDir,
+		}
+
+		for _, opt := range options {
+			opt(d)
+		}
 
-	if err := fileutils.MkdirAll(DefaultDataDir); err != nil {
-		panic(err)
+		d.dataDir = filepath.Join(d.workHome, "data")
+		d.pluginDir = filepath.Join(d.workHome, "plugins")
+		d.daemonSockPath = filepath.Join(d.workHome, "daemon.sock")
+		d.daemonLockPath = filepath.Join(d.workHome, "daemon.lock")
+		d.dfgetLockPath = filepath.Join(d.workHome, "dfget.lock")
+
+		// Create directories
+		for _, dir := range []string{d.workHome, d.cacheDir, d.logDir, d.dataDir, d.pluginDir} {
+			if err := fileutils.MkdirAll(dir); err != nil {
+				cache.err = err
+			}
+		}
+
+		cache.d = d
+	})
+
+	if cache.err != nil {
+		return nil, cache.err
 	}
+
+	d := *cache.d
+	return &d, nil
+}
+
+func (d *dfpath) WorkHome() string {
+	return d.workHome
+}
+
+func (d *dfpath) CacheDir() string {
+	return d.cacheDir
+}
+
+func (d *dfpath) LogDir() string {
+	return d.logDir
+}
+
+func (d *dfpath) DataDir() string {
+	return d.dataDir
+}
+
+func (d *dfpath) PluginDir() string {
+	return d.pluginDir
+}
+
+func (d *dfpath) DaemonSockPath() string {
+	return d.daemonSockPath
+}
+
+func (d *dfpath) DaemonLockPath() string {
+	return d.daemonLockPath
+}
+
+func (d *dfpath) DfgetLockPath() string {
+	return d.dfgetLockPath
 }
diff --git a/internal/dfpath/home_darwin.go b/internal/dfpath/dfpath_darwin.go
similarity index 70%
rename from internal/dfpath/home_darwin.go
rename to internal/dfpath/dfpath_darwin.go
index 84420fb6..3905daee 100644
--- a/internal/dfpath/home_darwin.go
+++ b/internal/dfpath/dfpath_darwin.go
@@ -1,3 +1,6 @@
+//go:build darwin
+// +build darwin
+
 /*
  *     Copyright 2020 The Dragonfly Authors
  *
@@ -22,4 +25,7 @@ import (
 	"d7y.io/dragonfly/v2/pkg/basic"
 )
 
-var WorkHome = filepath.Join(basic.HomeDir, ".dragonfly")
+var DefaultWorkHome = filepath.Join(basic.HomeDir, ".dragonfly")
+var DefaultCacheDir = filepath.Join(DefaultWorkHome, "cache")
+var DefaultConfigDir = filepath.Join(DefaultWorkHome, "config")
+var DefaultLogDir = filepath.Join(DefaultWorkHome, "logs")
diff --git a/internal/dfpath/cache_linux.go b/internal/dfpath/dfpath_linux.go
similarity index 80%
rename from internal/dfpath/cache_linux.go
rename to internal/dfpath/dfpath_linux.go
index 2d27da43..5db3041f 100644
--- a/internal/dfpath/cache_linux.go
+++ b/internal/dfpath/dfpath_linux.go
@@ -1,3 +1,6 @@
+//go:build linux
+// +build linux
+
 /*
  *     Copyright 2020 The Dragonfly Authors
  *
@@ -16,4 +19,7 @@
 
 package dfpath
 
+var DefaultWorkHome = "/usr/local/dragonfly"
 var DefaultCacheDir = "/var/cache/dragonfly"
+var DefaultConfigDir = "/etc/dragonfly"
+var DefaultLogDir = "/var/log/dragonfly"
diff --git a/internal/dfpath/home_linux.go b/internal/dfpath/home_linux.go
deleted file mode 100644
index a9496b91..00000000
--- a/internal/dfpath/home_linux.go
+++ /dev/null
@@ -1,19 +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 dfpath
-
-var WorkHome = "/usr/local/dragonfly"
diff --git a/internal/dfpath/log_darwin.go b/internal/dfpath/log_darwin.go
deleted file mode 100644
index a2dc944a..00000000
--- a/internal/dfpath/log_darwin.go
+++ /dev/null
@@ -1,23 +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 dfpath
-
-import (
-	"path/filepath"
-)
-
-var LogDir = filepath.Join(WorkHome, "logs")
diff --git a/internal/dfpath/log_linux.go b/internal/dfpath/log_linux.go
deleted file mode 100644
index 92eba5c4..00000000
--- a/internal/dfpath/log_linux.go
+++ /dev/null
@@ -1,19 +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 dfpath
-
-var LogDir = "/var/log/dragonfly"
diff --git a/internal/dfplugin/dfplugin.go b/internal/dfplugin/dfplugin.go
index 28d2cc29..aa23a72c 100644
--- a/internal/dfplugin/dfplugin.go
+++ b/internal/dfplugin/dfplugin.go
@@ -22,8 +22,6 @@ import (
 	"path"
 	"plugin"
 	"regexp"
-
-	"d7y.io/dragonfly/v2/internal/dfpath"
 )
 
 const (
@@ -52,9 +50,9 @@ const (
 
 type PluginInitFunc func(option map[string]string) (plugin interface{}, meta map[string]string, err error)
 
-func Load(typ PluginType, name string, option map[string]string) (interface{}, map[string]string, error) {
+func Load(dir string, typ PluginType, name string, option map[string]string) (interface{}, map[string]string, error) {
 	soName := fmt.Sprintf(PluginFormat, string(typ), name)
-	p, err := plugin.Open(path.Join(dfpath.PluginsDir, soName))
+	p, err := plugin.Open(path.Join(dir, soName))
 	if err != nil {
 		return nil, nil, err
 	}
diff --git a/manager/config/config.go b/manager/config/config.go
index 45d6e7b9..b53faaa9 100644
--- a/manager/config/config.go
+++ b/manager/config/config.go
@@ -33,6 +33,7 @@ type Config struct {
 
 type ServerConfig struct {
 	Name       string           `yaml:"name" mapstructure:"name"`
+	LogDir     string           `yaml:"logDir" mapstructure:"logDir"`
 	PublicPath string           `yaml:"publicPath" mapstructure:"publicPath"`
 	GRPC       *TCPListenConfig `yaml:"grpc" mapstructure:"grpc"`
 	REST       *RestConfig      `yaml:"rest" mapstructure:"rest"`
diff --git a/manager/manager.go b/manager/manager.go
index 6150342e..76e82fa0 100644
--- a/manager/manager.go
+++ b/manager/manager.go
@@ -24,6 +24,7 @@ import (
 	"google.golang.org/grpc"
 
 	logger "d7y.io/dragonfly/v2/internal/dflog"
+	"d7y.io/dragonfly/v2/internal/dfpath"
 	"d7y.io/dragonfly/v2/manager/cache"
 	"d7y.io/dragonfly/v2/manager/config"
 	"d7y.io/dragonfly/v2/manager/database"
@@ -55,7 +56,7 @@ type Server struct {
 	metricsServer *http.Server
 }
 
-func New(cfg *config.Config) (*Server, error) {
+func New(cfg *config.Config, d dfpath.Dfpath) (*Server, error) {
 	s := &Server{config: cfg}
 
 	// Initialize database
@@ -77,7 +78,7 @@ func New(cfg *config.Config) (*Server, error) {
 	}
 
 	// Initialize searcher
-	searcher := searcher.New()
+	searcher := searcher.New(d.PluginDir())
 
 	// Initialize job
 	job, err := job.New(cfg)
@@ -87,7 +88,7 @@ func New(cfg *config.Config) (*Server, error) {
 
 	// Initialize REST server
 	restService := service.NewREST(db, cache, job, enforcer)
-	router, err := router.Init(cfg, restService, enforcer)
+	router, err := router.Init(cfg, d.LogDir(), restService, enforcer)
 	if err != nil {
 		return nil, err
 	}
diff --git a/manager/router/router.go b/manager/router/router.go
index da65b65c..e33ed602 100644
--- a/manager/router/router.go
+++ b/manager/router/router.go
@@ -30,7 +30,6 @@ import (
 	ginSwagger "github.com/swaggo/gin-swagger"
 	"go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
 
-	"d7y.io/dragonfly/v2/internal/dfpath"
 	"d7y.io/dragonfly/v2/manager/config"
 	"d7y.io/dragonfly/v2/manager/handlers"
 	"d7y.io/dragonfly/v2/manager/middlewares"
@@ -44,7 +43,7 @@ const (
 
 var GinLogFileName = "gin.log"
 
-func Init(cfg *config.Config, service service.REST, enforcer *casbin.Enforcer) (*gin.Engine, error) {
+func Init(cfg *config.Config, logDir string, service service.REST, enforcer *casbin.Enforcer) (*gin.Engine, error) {
 	// Set mode
 	if !cfg.Verbose {
 		gin.SetMode(gin.ReleaseMode)
@@ -53,7 +52,7 @@ func Init(cfg *config.Config, service service.REST, enforcer *casbin.Enforcer) (
 	// Logging to a file
 	if !cfg.Console {
 		gin.DisableConsoleColor()
-		logDir := filepath.Join(dfpath.LogDir, "manager")
+		logDir := filepath.Join(logDir, "manager")
 		f, _ := os.Create(filepath.Join(logDir, GinLogFileName))
 		gin.DefaultWriter = io.MultiWriter(f)
 	}
diff --git a/manager/searcher/plugin.go b/manager/searcher/plugin.go
index fb0c7420..b52f5e98 100644
--- a/manager/searcher/plugin.go
+++ b/manager/searcher/plugin.go
@@ -26,8 +26,8 @@ const (
 	pluginName = "searcher"
 )
 
-func LoadPlugin() (Searcher, error) {
-	client, _, err := dfplugin.Load(dfplugin.PluginTypeManager, pluginName, map[string]string{})
+func LoadPlugin(dir string) (Searcher, error) {
+	client, _, err := dfplugin.Load(dir, dfplugin.PluginTypeManager, pluginName, map[string]string{})
 	if err != nil {
 		return nil, err
 	}
diff --git a/manager/searcher/searcher.go b/manager/searcher/searcher.go
index 63a588c5..c7b59dab 100644
--- a/manager/searcher/searcher.go
+++ b/manager/searcher/searcher.go
@@ -80,8 +80,8 @@ type Searcher interface {
 
 type searcher struct{}
 
-func New() Searcher {
-	s, err := LoadPlugin()
+func New(pluginDir string) Searcher {
+	s, err := LoadPlugin(pluginDir)
 	if err != nil {
 		logger.Info("use default searcher")
 		return &searcher{}
diff --git a/pkg/log/log.go b/pkg/log/log.go
index 66c18d72..dc4a2c1f 100644
--- a/pkg/log/log.go
+++ b/pkg/log/log.go
@@ -42,10 +42,18 @@ func SetupDaemon(logDir string, console bool) error {
 		return nil
 	}
 
-	if logDir == "" {
-		logDir = path.Join(dfpath.LogDir, "daemon")
+	var options []dfpath.Option
+	if logDir != "" {
+		options = append(options, dfpath.WithLogDir(logDir))
 	}
 
+	d, err := dfpath.New(options...)
+	if err != nil {
+		return err
+	}
+
+	logDir = path.Join(d.LogDir(), "daemon")
+
 	coreLogger, err := logcore.CreateLogger(path.Join(logDir, logcore.CoreLogFileName), false, false)
 	if err != nil {
 		return err
diff --git a/pkg/source/plugin.go b/pkg/source/plugin.go
index 72f88631..9fa0323e 100644
--- a/pkg/source/plugin.go
+++ b/pkg/source/plugin.go
@@ -27,10 +27,10 @@ const (
 	pluginMetadataScheme = "scheme"
 )
 
-func LoadPlugin(scheme string) (ResourceClient, error) {
+func LoadPlugin(dir, scheme string) (ResourceClient, error) {
 	// TODO init option
 	logger.Debugf("try to load source plugin: %s", scheme)
-	client, meta, err := dfplugin.Load(dfplugin.PluginTypeResource, scheme, map[string]string{})
+	client, meta, err := dfplugin.Load(dir, dfplugin.PluginTypeResource, scheme, map[string]string{})
 	if err != nil {
 		logger.Errorf("load source plugin error: %s", err)
 		return nil, err
diff --git a/pkg/source/source_client.go b/pkg/source/source_client.go
index d7f3062e..53dc0e34 100644
--- a/pkg/source/source_client.go
+++ b/pkg/source/source_client.go
@@ -118,13 +118,14 @@ type ClientManager interface {
 	UnRegister(scheme string)
 
 	// GetClient a source client by scheme
-	GetClient(scheme string) (ResourceClient, bool)
+	GetClient(scheme string, options ...Option) (ResourceClient, bool)
 }
 
 // clientManager implements the interface ClientManager
 type clientManager struct {
-	mu      sync.RWMutex
-	clients map[string]ResourceClient
+	mu        sync.RWMutex
+	clients   map[string]ResourceClient
+	pluginDir string
 }
 
 var _ ClientManager = (*clientManager)(nil)
@@ -137,6 +138,14 @@ func NewManager() ClientManager {
 	}
 }
 
+type Option func(c *clientManager)
+
+func WithPluginDir(dir string) Option {
+	return func(c *clientManager) {
+		c.pluginDir = dir
+	}
+}
+
 func (m *clientManager) Register(scheme string, resourceClient ResourceClient, adaptor requestAdapter, hooks ...Hook) error {
 	m.mu.Lock()
 	defer m.mu.Unlock()
@@ -162,7 +171,7 @@ func (m *clientManager) UnRegister(scheme string) {
 	delete(m.clients, strings.ToLower(scheme))
 }
 
-func (m *clientManager) GetClient(scheme string) (ResourceClient, bool) {
+func (m *clientManager) GetClient(scheme string, options ...Option) (ResourceClient, bool) {
 	logger.Debugf("current clients: %#v", m.clients)
 	m.mu.RLock()
 	scheme = strings.ToLower(scheme)
@@ -178,7 +187,12 @@ func (m *clientManager) GetClient(scheme string) (ResourceClient, bool) {
 		m.mu.Unlock()
 		return client, true
 	}
-	client, err := LoadPlugin(scheme)
+
+	for _, opt := range options {
+		opt(m)
+	}
+
+	client, err := LoadPlugin(m.pluginDir, scheme)
 	if err != nil {
 		logger.Errorf("failed to load source plugin for scheme %s: %v", scheme, err)
 		m.mu.Unlock()
diff --git a/scheduler/config/config.go b/scheduler/config/config.go
index f82c8040..d3c80178 100644
--- a/scheduler/config/config.go
+++ b/scheduler/config/config.go
@@ -192,9 +192,11 @@ type SchedulerConfig struct {
 }
 
 type ServerConfig struct {
-	IP   string `yaml:"ip" mapstructure:"ip"`
-	Host string `yaml:"host" mapstructure:"host"`
-	Port int    `yaml:"port" mapstructure:"port"`
+	IP       string `yaml:"ip" mapstructure:"ip"`
+	Host     string `yaml:"host" mapstructure:"host"`
+	Port     int    `yaml:"port" mapstructure:"port"`
+	CacheDir string `yaml:"cacheDir" mapstructure:"cacheDir"`
+	LogDir   string `yaml:"logDir" mapstructure:"logDir"`
 }
 
 type GCConfig struct {
diff --git a/scheduler/config/dynconfig.go b/scheduler/config/dynconfig.go
index 97b80e2f..1a8c317f 100644
--- a/scheduler/config/dynconfig.go
+++ b/scheduler/config/dynconfig.go
@@ -24,7 +24,6 @@ import (
 	"time"
 
 	logger "d7y.io/dragonfly/v2/internal/dflog"
-	"d7y.io/dragonfly/v2/internal/dfpath"
 	dc "d7y.io/dragonfly/v2/internal/dynconfig"
 	"d7y.io/dragonfly/v2/manager/types"
 	"d7y.io/dragonfly/v2/pkg/rpc/manager"
@@ -32,10 +31,7 @@ import (
 )
 
 var (
-	cachePath = filepath.Join(dfpath.DefaultCacheDir, "scheduler_dynconfig")
-)
-
-var (
+	cacheFileName = "scheduler_dynconfig"
 	watchInterval = 1 * time.Second
 )
 
@@ -117,16 +113,19 @@ type dynconfig struct {
 	observers  map[Observer]struct{}
 	done       chan bool
 	cdnDirPath string
+	cachePath  string
 	sourceType dc.SourceType
 }
 
 // TODO(Gaius) Rely on manager to delete cdnDirPath
-func NewDynconfig(sourceType dc.SourceType, cdnDirPath string, options ...dc.Option) (DynconfigInterface, error) {
+func NewDynconfig(sourceType dc.SourceType, cacheDir string, cdnDirPath string, options ...dc.Option) (DynconfigInterface, error) {
+	cachePath := filepath.Join(cacheDir, cacheFileName)
 	d := &dynconfig{
 		observers:  map[Observer]struct{}{},
 		done:       make(chan bool),
 		cdnDirPath: cdnDirPath,
 		sourceType: sourceType,
+		cachePath:  cachePath,
 	}
 
 	options = append(options, dc.WithCachePath(cachePath))
@@ -312,7 +311,7 @@ func (d *dynconfig) watch() {
 
 func (d *dynconfig) Stop() error {
 	close(d.done)
-	if err := os.Remove(cachePath); err != nil {
+	if err := os.Remove(d.cachePath); err != nil {
 		return err
 	}
 
diff --git a/scheduler/core/evaluator/evaluator.go b/scheduler/core/evaluator/evaluator.go
index a99b0dc3..2ae8e2bb 100644
--- a/scheduler/core/evaluator/evaluator.go
+++ b/scheduler/core/evaluator/evaluator.go
@@ -42,10 +42,10 @@ type Evaluator interface {
 	IsBadNode(peer *supervisor.Peer) bool
 }
 
-func New(algorithm string) Evaluator {
+func New(algorithm string, pluginDir string) Evaluator {
 	switch algorithm {
 	case PluginAlgorithm:
-		if plugin, err := LoadPlugin(); err == nil {
+		if plugin, err := LoadPlugin(pluginDir); err == nil {
 			return plugin
 		}
 	// TODO Implement MLAlgorithm
diff --git a/scheduler/core/evaluator/plugin.go b/scheduler/core/evaluator/plugin.go
index 41a6caa9..27472376 100644
--- a/scheduler/core/evaluator/plugin.go
+++ b/scheduler/core/evaluator/plugin.go
@@ -26,8 +26,8 @@ const (
 	pluginName = "evaluator"
 )
 
-func LoadPlugin() (Evaluator, error) {
-	client, _, err := dfplugin.Load(dfplugin.PluginTypeScheduler, pluginName, map[string]string{})
+func LoadPlugin(dir string) (Evaluator, error) {
+	client, _, err := dfplugin.Load(dir, dfplugin.PluginTypeScheduler, pluginName, map[string]string{})
 	if err != nil {
 		return nil, err
 	}
diff --git a/scheduler/core/scheduler/basic/basic_scheduler.go b/scheduler/core/scheduler/basic/basic_scheduler.go
index dd7fedf1..848201a0 100644
--- a/scheduler/core/scheduler/basic/basic_scheduler.go
+++ b/scheduler/core/scheduler/basic/basic_scheduler.go
@@ -46,7 +46,7 @@ func newBasicSchedulerBuilder() scheduler.Builder {
 
 func (builder *basicSchedulerBuilder) Build(cfg *config.SchedulerConfig, opts *scheduler.BuildOptions) (scheduler.Scheduler, error) {
 	logger.Debugf("start create basic scheduler...")
-	evaluator := evaluator.New(cfg.Algorithm)
+	evaluator := evaluator.New(cfg.Algorithm, opts.PluginDir)
 	sched := &Scheduler{
 		evaluator:   evaluator,
 		peerManager: opts.PeerManager,
diff --git a/scheduler/core/scheduler/scheduler.go b/scheduler/core/scheduler/scheduler.go
index 4df67a69..5abfbb8e 100644
--- a/scheduler/core/scheduler/scheduler.go
+++ b/scheduler/core/scheduler/scheduler.go
@@ -36,6 +36,7 @@ type Scheduler interface {
 type BuildOptions struct {
 	TaskManager supervisor.TaskManager
 	PeerManager supervisor.PeerManager
+	PluginDir   string
 }
 
 var (
diff --git a/scheduler/core/service.go b/scheduler/core/service.go
index c4a8e780..4f5aaae8 100644
--- a/scheduler/core/service.go
+++ b/scheduler/core/service.go
@@ -83,7 +83,7 @@ type SchedulerService struct {
 	metricsConfig *config.MetricsConfig
 }
 
-func NewSchedulerService(cfg *config.SchedulerConfig, metricsConfig *config.MetricsConfig, dynConfig config.DynconfigInterface, gc gc.GC, options ...Option) (*SchedulerService, error) {
+func NewSchedulerService(cfg *config.SchedulerConfig, pluginDir string, metricsConfig *config.MetricsConfig, dynConfig config.DynconfigInterface, gc gc.GC, options ...Option) (*SchedulerService, error) {
 	ops := &Options{}
 	for _, op := range options {
 		op(ops)
@@ -103,6 +103,7 @@ func NewSchedulerService(cfg *config.SchedulerConfig, metricsConfig *config.Metr
 
 	sched, err := scheduler.Get(cfg.Scheduler).Build(cfg, &scheduler.BuildOptions{
 		PeerManager: peerManager,
+		PluginDir:   pluginDir,
 	})
 	if err != nil {
 		return nil, errors.Wrapf(err, "build scheduler %v", cfg.Scheduler)
diff --git a/scheduler/scheduler.go b/scheduler/scheduler.go
index 1da7df68..24a26411 100644
--- a/scheduler/scheduler.go
+++ b/scheduler/scheduler.go
@@ -26,6 +26,7 @@ import (
 
 	"d7y.io/dragonfly/v2/cmd/dependency"
 	logger "d7y.io/dragonfly/v2/internal/dflog"
+	"d7y.io/dragonfly/v2/internal/dfpath"
 	"d7y.io/dragonfly/v2/internal/dynconfig"
 	"d7y.io/dragonfly/v2/pkg/gc"
 	"d7y.io/dragonfly/v2/pkg/rpc"
@@ -68,7 +69,7 @@ type Server struct {
 	gc gc.GC
 }
 
-func New(cfg *config.Config) (*Server, error) {
+func New(cfg *config.Config, d dfpath.Dfpath) (*Server, error) {
 	s := &Server{config: cfg}
 
 	// Initialize manager client
@@ -101,7 +102,7 @@ func New(cfg *config.Config) (*Server, error) {
 			dynconfig.WithExpireTime(cfg.DynConfig.ExpireTime),
 		)
 	}
-	dynConfig, err := config.NewDynconfig(cfg.DynConfig.Type, cfg.DynConfig.CDNDirPath, options...)
+	dynConfig, err := config.NewDynconfig(cfg.DynConfig.Type, d.CacheDir(), cfg.DynConfig.CDNDirPath, options...)
 	if err != nil {
 		return nil, err
 	}
@@ -115,7 +116,7 @@ func New(cfg *config.Config) (*Server, error) {
 	if cfg.Options.Telemetry.Jaeger != "" {
 		openTel = true
 	}
-	service, err := core.NewSchedulerService(cfg.Scheduler, cfg.Metrics, dynConfig, s.gc, core.WithDisableCDN(cfg.DisableCDN), core.WithOpenTel(openTel))
+	service, err := core.NewSchedulerService(cfg.Scheduler, d.PluginDir(), cfg.Metrics, dynConfig, s.gc, core.WithDisableCDN(cfg.DisableCDN), core.WithOpenTel(openTel))
 	if err != nil {
 		return nil, err
 	}


Test patch:
diff --git a/client/config/dynconfig_test.go b/client/config/dynconfig_test.go
index d7278e91..e3462d45 100644
--- a/client/config/dynconfig_test.go
+++ b/client/config/dynconfig_test.go
@@ -17,7 +17,9 @@
 package config
 
 import (
+	"io/ioutil"
 	"os"
+	"path/filepath"
 	"testing"
 	"time"
 
@@ -30,6 +32,12 @@ import (
 )
 
 func TestDynconfigNewDynconfig(t *testing.T) {
+	mockCacheDir, err := ioutil.TempDir("", "dragonfly-test")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	mockCachePath := filepath.Join(mockCacheDir, cacheFileName)
 	tests := []struct {
 		name           string
 		expire         time.Duration
@@ -45,7 +53,7 @@ func TestDynconfigNewDynconfig(t *testing.T) {
 				Hostname: "foo",
 			},
 			cleanFileCache: func(t *testing.T) {
-				if err := os.Remove(cachePath); err != nil {
+				if err := os.Remove(mockCachePath); err != nil {
 					t.Fatal(err)
 				}
 			},
@@ -62,7 +70,7 @@ func TestDynconfigNewDynconfig(t *testing.T) {
 			expire:     10 * time.Millisecond,
 			hostOption: HostOption{},
 			cleanFileCache: func(t *testing.T) {
-				if err := os.Remove(cachePath); err != nil {
+				if err := os.Remove(mockCachePath); err != nil {
 					t.Fatal(err)
 				}
 			},
@@ -109,7 +117,7 @@ func TestDynconfigNewDynconfig(t *testing.T) {
 
 			mockManagerClient := mocks.NewMockClient(ctl)
 			tc.mock(mockManagerClient.EXPECT())
-			_, err := NewDynconfig(mockManagerClient, tc.hostOption, tc.expire)
+			_, err := NewDynconfig(mockManagerClient, mockCacheDir, tc.hostOption, tc.expire)
 			tc.expect(t, err)
 			tc.cleanFileCache(t)
 		})
@@ -117,6 +125,12 @@ func TestDynconfigNewDynconfig(t *testing.T) {
 }
 
 func TestDynconfigGet(t *testing.T) {
+	mockCacheDir, err := ioutil.TempDir("", "dragonfly-test")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	mockCachePath := filepath.Join(mockCacheDir, cacheFileName)
 	tests := []struct {
 		name           string
 		expire         time.Duration
@@ -142,7 +156,7 @@ func TestDynconfigGet(t *testing.T) {
 			},
 			sleep: func() {},
 			cleanFileCache: func(t *testing.T) {
-				if err := os.Remove(cachePath); err != nil {
+				if err := os.Remove(mockCachePath); err != nil {
 					t.Fatal(err)
 				}
 			},
@@ -181,7 +195,7 @@ func TestDynconfigGet(t *testing.T) {
 				time.Sleep(100 * time.Millisecond)
 			},
 			cleanFileCache: func(t *testing.T) {
-				if err := os.Remove(cachePath); err != nil {
+				if err := os.Remove(mockCachePath); err != nil {
 					t.Fatal(err)
 				}
 			},
@@ -221,7 +235,7 @@ func TestDynconfigGet(t *testing.T) {
 				time.Sleep(100 * time.Millisecond)
 			},
 			cleanFileCache: func(t *testing.T) {
-				if err := os.Remove(cachePath); err != nil {
+				if err := os.Remove(mockCachePath); err != nil {
 					t.Fatal(err)
 				}
 			},
@@ -257,7 +271,7 @@ func TestDynconfigGet(t *testing.T) {
 				time.Sleep(100 * time.Millisecond)
 			},
 			cleanFileCache: func(t *testing.T) {
-				if err := os.Remove(cachePath); err != nil {
+				if err := os.Remove(mockCachePath); err != nil {
 					t.Fatal(err)
 				}
 			},
@@ -283,7 +297,7 @@ func TestDynconfigGet(t *testing.T) {
 
 			mockManagerClient := mocks.NewMockClient(ctl)
 			tc.mock(mockManagerClient.EXPECT(), tc.data)
-			dynconfig, err := NewDynconfig(mockManagerClient, tc.hostOption, tc.expire)
+			dynconfig, err := NewDynconfig(mockManagerClient, mockCacheDir, tc.hostOption, tc.expire)
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -296,6 +310,12 @@ func TestDynconfigGet(t *testing.T) {
 }
 
 func TestDynconfigGetSchedulers(t *testing.T) {
+	mockCacheDir, err := ioutil.TempDir("", "dragonfly-test")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	mockCachePath := filepath.Join(mockCacheDir, cacheFileName)
 	tests := []struct {
 		name           string
 		expire         time.Duration
@@ -321,7 +341,7 @@ func TestDynconfigGetSchedulers(t *testing.T) {
 			},
 			sleep: func() {},
 			cleanFileCache: func(t *testing.T) {
-				if err := os.Remove(cachePath); err != nil {
+				if err := os.Remove(mockCachePath); err != nil {
 					t.Fatal(err)
 				}
 			},
@@ -360,7 +380,7 @@ func TestDynconfigGetSchedulers(t *testing.T) {
 				time.Sleep(100 * time.Millisecond)
 			},
 			cleanFileCache: func(t *testing.T) {
-				if err := os.Remove(cachePath); err != nil {
+				if err := os.Remove(mockCachePath); err != nil {
 					t.Fatal(err)
 				}
 			},
@@ -400,7 +420,7 @@ func TestDynconfigGetSchedulers(t *testing.T) {
 				time.Sleep(100 * time.Millisecond)
 			},
 			cleanFileCache: func(t *testing.T) {
-				if err := os.Remove(cachePath); err != nil {
+				if err := os.Remove(mockCachePath); err != nil {
 					t.Fatal(err)
 				}
 			},
@@ -436,7 +456,7 @@ func TestDynconfigGetSchedulers(t *testing.T) {
 				time.Sleep(100 * time.Millisecond)
 			},
 			cleanFileCache: func(t *testing.T) {
-				if err := os.Remove(cachePath); err != nil {
+				if err := os.Remove(mockCachePath); err != nil {
 					t.Fatal(err)
 				}
 			},
@@ -462,7 +482,7 @@ func TestDynconfigGetSchedulers(t *testing.T) {
 
 			mockManagerClient := mocks.NewMockClient(ctl)
 			tc.mock(mockManagerClient.EXPECT(), tc.data)
-			dynconfig, err := NewDynconfig(mockManagerClient, tc.hostOption, tc.expire)
+			dynconfig, err := NewDynconfig(mockManagerClient, mockCacheDir, tc.hostOption, tc.expire)
 			if err != nil {
 				t.Fatal(err)
 			}
diff --git a/client/config/peerhost_test.go b/client/config/peerhost_test.go
index 67e1931f..fff4b46e 100644
--- a/client/config/peerhost_test.go
+++ b/client/config/peerhost_test.go
@@ -224,7 +224,6 @@ func TestPeerHostOption_Load(t *testing.T) {
 		GCInterval: clientutil.Duration{
 			Duration: 60000000000,
 		},
-		DataDir:     "/tmp/dragonfly/dfdaemon/",
 		WorkHome:    "/tmp/dragonfly/dfdaemon/",
 		KeepStorage: false,
 		Scheduler: SchedulerOption{
diff --git a/client/config/testdata/config/daemon.yaml b/client/config/testdata/config/daemon.yaml
index a5f5f1a9..63d24d35 100644
--- a/client/config/testdata/config/daemon.yaml
+++ b/client/config/testdata/config/daemon.yaml
@@ -1,6 +1,5 @@
 aliveTime: 0s
 gcInterval: 1m0s
-dataDir: /tmp/dragonfly/dfdaemon/
 workHome: /tmp/dragonfly/dfdaemon/
 keepStorage: false
 scheduler:
diff --git a/internal/dfpath/dfpath_test.go b/internal/dfpath/dfpath_test.go
new file mode 100644
index 00000000..aeb048e2
--- /dev/null
+++ b/internal/dfpath/dfpath_test.go
@@ -0,0 +1,85 @@
+/*
+ *     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 dfpath
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestNew(t *testing.T) {
+	tests := []struct {
+		name    string
+		options []Option
+		expect  func(t *testing.T, options []Option)
+	}{
+		{
+			name: "new dfpath succeeded",
+			expect: func(t *testing.T, options []Option) {
+				assert := assert.New(t)
+				d, err := New(options...)
+				assert.NoError(err)
+				assert.Equal(d.WorkHome(), DefaultWorkHome)
+				assert.Equal(d.CacheDir(), DefaultCacheDir)
+				assert.Equal(d.LogDir(), DefaultLogDir)
+			},
+		},
+		{
+			name:    "new dfpath succeeded by workHome",
+			options: []Option{WithWorkHome("foo")},
+			expect: func(t *testing.T, options []Option) {
+				assert := assert.New(t)
+				d, err := New(options...)
+				assert.NoError(err)
+				assert.Equal(d.WorkHome(), DefaultWorkHome)
+				assert.Equal(d.CacheDir(), DefaultCacheDir)
+				assert.Equal(d.LogDir(), DefaultLogDir)
+			},
+		},
+		{
+			name:    "new dfpath succeeded by cacheDir",
+			options: []Option{WithCacheDir("foo")},
+			expect: func(t *testing.T, options []Option) {
+				assert := assert.New(t)
+				d, err := New(options...)
+				assert.NoError(err)
+				assert.Equal(d.WorkHome(), DefaultWorkHome)
+				assert.Equal(d.CacheDir(), DefaultCacheDir)
+				assert.Equal(d.LogDir(), DefaultLogDir)
+			},
+		},
+		{
+			name:    "new dfpath succeeded by logDir",
+			options: []Option{WithLogDir("foo")},
+			expect: func(t *testing.T, options []Option) {
+				assert := assert.New(t)
+				d, err := New(options...)
+				assert.NoError(err)
+				assert.Equal(d.WorkHome(), DefaultWorkHome)
+				assert.Equal(d.CacheDir(), DefaultCacheDir)
+				assert.Equal(d.LogDir(), DefaultLogDir)
+			},
+		},
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			tc.expect(t, tc.options)
+		})
+	}
+}
diff --git a/manager/config/config_test.go b/manager/config/config_test.go
index 67569ae9..f894bdc9 100644
--- a/manager/config/config_test.go
+++ b/manager/config/config_test.go
@@ -30,7 +30,9 @@ func TestManagerConfig_Load(t *testing.T) {
 
 	config := &Config{
 		Server: &ServerConfig{
-			Name: "foo",
+			Name:       "foo",
+			LogDir:     "foo",
+			PublicPath: "foo",
 			GRPC: &TCPListenConfig{
 				Listen: "127.0.0.1",
 				PortRange: TCPListenPortRange{
diff --git a/manager/config/testdata/manager.yaml b/manager/config/testdata/manager.yaml
index 3b35c8db..8bf968a0 100644
--- a/manager/config/testdata/manager.yaml
+++ b/manager/config/testdata/manager.yaml
@@ -1,5 +1,7 @@
 server:
   name: foo
+  logDir: foo
+  publicPath: foo
   grpc:
     listen: 127.0.0.1
     port:
diff --git a/manager/searcher/searcher_test.go b/manager/searcher/searcher_test.go
index ceff2b87..2e02b2f0 100644
--- a/manager/searcher/searcher_test.go
+++ b/manager/searcher/searcher_test.go
@@ -27,6 +27,7 @@ import (
 )
 
 func TestSchedulerCluster(t *testing.T) {
+	pluginDir := "."
 	tests := []struct {
 		name              string
 		schedulerClusters []model.SchedulerCluster
@@ -374,7 +375,7 @@ func TestSchedulerCluster(t *testing.T) {
 
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
-			searcher := New()
+			searcher := New(pluginDir)
 			clusters, ok := searcher.FindSchedulerCluster(context.Background(), tc.schedulerClusters, &manager.ListSchedulersRequest{
 				HostName: "foo",
 				Ip:       "127.0.0.1",
diff --git a/manager/searcher/testdata/main.go b/manager/searcher/testdata/main.go
index 7240adee..2f6326ef 100644
--- a/manager/searcher/testdata/main.go
+++ b/manager/searcher/testdata/main.go
@@ -18,24 +18,16 @@ package main
 
 import (
 	"context"
-	"flag"
 	"fmt"
 	"os"
 
-	"d7y.io/dragonfly/v2/internal/dfpath"
 	"d7y.io/dragonfly/v2/manager/model"
 	"d7y.io/dragonfly/v2/manager/searcher"
 	"d7y.io/dragonfly/v2/pkg/rpc/manager"
 )
 
-func init() {
-	flag.StringVar(&dfpath.PluginsDir, "plugin-dir", ".", "")
-}
-
 func main() {
-	flag.Parse()
-
-	s, err := searcher.LoadPlugin()
+	s, err := searcher.LoadPlugin("./testdata")
 	if err != nil {
 		fmt.Printf("load plugin error: %s\n", err)
 		os.Exit(1)
diff --git a/pkg/source/testdata/main.go b/pkg/source/testdata/main.go
index 55b3d853..8ffbd3ae 100644
--- a/pkg/source/testdata/main.go
+++ b/pkg/source/testdata/main.go
@@ -17,23 +17,15 @@
 package main
 
 import (
-	"flag"
 	"fmt"
 	"io/ioutil"
 	"os"
 
-	"d7y.io/dragonfly/v2/internal/dfpath"
 	"d7y.io/dragonfly/v2/pkg/source"
 )
 
-func init() {
-	flag.StringVar(&dfpath.PluginsDir, "plugin-dir", ".", "")
-}
-
 func main() {
-	flag.Parse()
-
-	client, err := source.LoadPlugin("dfs")
+	client, err := source.LoadPlugin("./testdata", "dfs")
 	if err != nil {
 		fmt.Printf("load plugin error: %s\n", err)
 		os.Exit(1)
diff --git a/scheduler/config/config_test.go b/scheduler/config/config_test.go
index 1f406083..7bb96759 100644
--- a/scheduler/config/config_test.go
+++ b/scheduler/config/config_test.go
@@ -43,9 +43,11 @@ func TestSchedulerConfig_Load(t *testing.T) {
 			WorkerNum: 8,
 		},
 		Server: &ServerConfig{
-			IP:   "127.0.0.1",
-			Host: "foo",
-			Port: 8002,
+			IP:       "127.0.0.1",
+			Host:     "foo",
+			Port:     8002,
+			CacheDir: "foo",
+			LogDir:   "foo",
 		},
 		Manager: &ManagerConfig{
 			Addr:               "127.0.0.1:65003",
diff --git a/scheduler/config/dynconfig_test.go b/scheduler/config/dynconfig_test.go
index 5280311d..d620bc9d 100644
--- a/scheduler/config/dynconfig_test.go
+++ b/scheduler/config/dynconfig_test.go
@@ -17,6 +17,7 @@
 package config
 
 import (
+	"io/ioutil"
 	"os"
 	"path/filepath"
 	"testing"
@@ -32,6 +33,12 @@ import (
 )
 
 func TestDynconfigGet_ManagerSourceType(t *testing.T) {
+	mockCacheDir, err := ioutil.TempDir("", "dragonfly-test")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	mockCachePath := filepath.Join(mockCacheDir, cacheFileName)
 	tests := []struct {
 		name           string
 		expire         time.Duration
@@ -44,7 +51,7 @@ func TestDynconfigGet_ManagerSourceType(t *testing.T) {
 			name:   "get dynconfig success",
 			expire: 10 * time.Second,
 			cleanFileCache: func(t *testing.T) {
-				if err := os.Remove(cachePath); err != nil {
+				if err := os.Remove(mockCachePath); err != nil {
 					t.Fatal(err)
 				}
 			},
@@ -73,7 +80,7 @@ func TestDynconfigGet_ManagerSourceType(t *testing.T) {
 			name:   "client failed to return for the second time",
 			expire: 10 * time.Millisecond,
 			cleanFileCache: func(t *testing.T) {
-				if err := os.Remove(cachePath); err != nil {
+				if err := os.Remove(mockCachePath); err != nil {
 					t.Fatal(err)
 				}
 			},
@@ -112,12 +119,11 @@ func TestDynconfigGet_ManagerSourceType(t *testing.T) {
 			mockManagerClient := mocks.NewMockClient(ctl)
 			tc.mock(mockManagerClient.EXPECT())
 
-			d, err := NewDynconfig(dc.ManagerSourceType, "", []dc.Option{
+			d, err := NewDynconfig(dc.ManagerSourceType, mockCacheDir, "", []dc.Option{
 				dc.WithManagerClient(NewManagerClient(mockManagerClient, &Config{
 					Manager: &ManagerConfig{SchedulerClusterID: uint(1)},
 					Server:  &ServerConfig{Host: "foo"},
 				})),
-				dc.WithCachePath(cachePath),
 				dc.WithExpireTime(tc.expire),
 			}...)
 			if err != nil {
@@ -133,6 +139,11 @@ func TestDynconfigGet_ManagerSourceType(t *testing.T) {
 }
 
 func TestDynconfigGet_LocalSourceType(t *testing.T) {
+	mockCacheDir, err := ioutil.TempDir("", "dragonfly-test")
+	if err != nil {
+		t.Fatal(err)
+	}
+
 	tests := []struct {
 		name       string
 		configPath string
@@ -174,7 +185,7 @@ func TestDynconfigGet_LocalSourceType(t *testing.T) {
 
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
-			d, err := NewDynconfig(dc.LocalSourceType, "", dc.WithLocalConfigPath(tc.configPath))
+			d, err := NewDynconfig(dc.LocalSourceType, mockCacheDir, "", dc.WithLocalConfigPath(tc.configPath))
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -186,6 +197,11 @@ func TestDynconfigGet_LocalSourceType(t *testing.T) {
 }
 
 func TestDynconfigGetCDNFromDirPath(t *testing.T) {
+	mockCacheDir, err := ioutil.TempDir("", "dragonfly-test")
+	if err != nil {
+		t.Fatal(err)
+	}
+
 	tests := []struct {
 		name       string
 		cdnDirPath string
@@ -217,7 +233,7 @@ func TestDynconfigGetCDNFromDirPath(t *testing.T) {
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
 
-			d, err := NewDynconfig(dc.LocalSourceType, tc.cdnDirPath, dc.WithLocalConfigPath("./testdata/scheduler.yaml"))
+			d, err := NewDynconfig(dc.LocalSourceType, mockCacheDir, tc.cdnDirPath, dc.WithLocalConfigPath("./testdata/scheduler.yaml"))
 			if err != nil {
 				t.Fatal(err)
 			}
diff --git a/scheduler/config/testdata/scheduler.yaml b/scheduler/config/testdata/scheduler.yaml
index 5c15aa66..aa7c7c4e 100644
--- a/scheduler/config/testdata/scheduler.yaml
+++ b/scheduler/config/testdata/scheduler.yaml
@@ -14,6 +14,8 @@ server:
   ip: "127.0.0.1"
   host: "foo"
   port: 8002
+  cacheDir: "foo"
+  logDir: "foo"
 
 cdn:
   servers:
diff --git a/scheduler/core/evaluator/evaluator_test.go b/scheduler/core/evaluator/evaluator_test.go
index daaf7b44..f5fc0ebd 100644
--- a/scheduler/core/evaluator/evaluator_test.go
+++ b/scheduler/core/evaluator/evaluator_test.go
@@ -24,6 +24,7 @@ import (
 )
 
 func TestEvaluatorNew(t *testing.T) {
+	pluginDir := "."
 	tests := []struct {
 		name      string
 		algorithm string
@@ -65,7 +66,7 @@ func TestEvaluatorNew(t *testing.T) {
 
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
-			tc.expect(t, New(tc.algorithm))
+			tc.expect(t, New(tc.algorithm, pluginDir))
 		})
 	}
 }
diff --git a/scheduler/core/evaluator/testdata/main.go b/scheduler/core/evaluator/testdata/main.go
index 0ab8c10e..de78aca5 100644
--- a/scheduler/core/evaluator/testdata/main.go
+++ b/scheduler/core/evaluator/testdata/main.go
@@ -17,23 +17,15 @@
 package main
 
 import (
-	"flag"
 	"fmt"
 	"os"
 
-	"d7y.io/dragonfly/v2/internal/dfpath"
 	"d7y.io/dragonfly/v2/scheduler/core/evaluator"
 	"d7y.io/dragonfly/v2/scheduler/supervisor"
 )
 
-func init() {
-	flag.StringVar(&dfpath.PluginsDir, "plugin-dir", ".", "")
-}
-
 func main() {
-	flag.Parse()
-
-	e, err := evaluator.LoadPlugin()
+	e, err := evaluator.LoadPlugin("./testdata")
 	if err != nil {
 		fmt.Printf("load plugin error: %s\n", err)
 		os.Exit(1)