Repository: dragonflyoss/dragonfly

Issue:
Refactor seed peer pick logic in scheduler
The original query overrides some gRPC resolve methods, making the overall process rather convoluted. We can simplify the procedure by directly fetching the seed peer from the in-memory host manager in `TriggerDownloadTask` and performing a consistent hash to pick the seed peer.

Discussion:
@chlins: Closed by https://github.com/dragonflyoss/dragonfly/pull/4235.

Solution patch:
diff --git a/scheduler/job/job.go b/scheduler/job/job.go
index af9042d4..cb425d0d 100644
--- a/scheduler/job/job.go
+++ b/scheduler/job/job.go
@@ -39,12 +39,13 @@ import (
 	commonv1 "d7y.io/api/v2/pkg/apis/common/v1"
 	commonv2 "d7y.io/api/v2/pkg/apis/common/v2"
 	dfdaemonv2 "d7y.io/api/v2/pkg/apis/dfdaemon/v2"
-	managerv2 "d7y.io/api/v2/pkg/apis/manager/v2"
 
 	logger "d7y.io/dragonfly/v2/internal/dflog"
 	internaljob "d7y.io/dragonfly/v2/internal/job"
 	managertypes "d7y.io/dragonfly/v2/manager/types"
+	"d7y.io/dragonfly/v2/pkg/dfnet"
 	"d7y.io/dragonfly/v2/pkg/idgen"
+	cndsystemclient "d7y.io/dragonfly/v2/pkg/rpc/cdnsystem/client"
 	dfdaemonclient "d7y.io/dragonfly/v2/pkg/rpc/dfdaemon/client"
 	"d7y.io/dragonfly/v2/scheduler/config"
 	resource "d7y.io/dragonfly/v2/scheduler/resource/standard"
@@ -79,10 +80,11 @@ type job struct {
 	localJob     *internaljob.Job
 	resource     resource.Resource
 	config       *config.Config
+	dialOptions  []grpc.DialOption
 }
 
 // New creates a new Job.
-func New(cfg *config.Config, resource resource.Resource) (Job, error) {
+func New(cfg *config.Config, resource resource.Resource, dialOptions ...grpc.DialOption) (Job, error) {
 	redisConfig := &internaljob.Config{
 		Addrs:            cfg.Database.Redis.Addrs,
 		MasterName:       cfg.Database.Redis.MasterName,
@@ -127,6 +129,7 @@ func New(cfg *config.Config, resource resource.Resource) (Job, error) {
 		localJob:     localJob,
 		resource:     resource,
 		config:       cfg,
+		dialOptions:  dialOptions,
 	}
 
 	namedJobFuncs := map[string]any{
@@ -241,11 +244,6 @@ func (j *job) PreheatSingleSeedPeer(ctx context.Context, req *internaljob.Prehea
 		return nil, fmt.Errorf("cluster %d scheduler %s has disabled seed peer", j.config.Manager.SchedulerClusterID, j.config.Server.AdvertiseIP)
 	}
 
-	// If scheduler has no available seed peer, return error.
-	if len(j.resource.SeedPeer().Client().Addrs()) == 0 {
-		return nil, fmt.Errorf("cluster %d scheduler %s has no available seed peer", j.config.Manager.SchedulerClusterID, j.config.Server.AdvertiseIP)
-	}
-
 	// Preheat by v2 grpc protocol. If seed peer does not support
 	// v2 protocol, preheat by v1 grpc protocol.
 	resp, err := j.preheatV2SingleSeedPeer(ctx, req, log)
@@ -275,8 +273,23 @@ func (j *job) preheatV1SingleSeedPeer(ctx context.Context, req *internaljob.Preh
 		Priority:    commonv1.Priority(req.Priority),
 	}
 
+	selected, err := j.resource.SeedPeer().Select(ctx, taskID)
+	if err != nil {
+		return nil, err
+	}
+
+	addr := fmt.Sprintf("%s:%d", selected.IP, selected.Port)
+	log.Infof("[preheat]: selected seed peer %s", addr)
+
+	// TODO(chlins): reuse the client if we encounter the performance issue in future.
+	client, err := cndsystemclient.GetClientByAddr(ctx, dfnet.NetAddr{Type: dfnet.TCP, Addr: addr}, j.dialOptions...)
+	if err != nil {
+		return nil, err
+	}
+	defer client.Close()
+
 	// Trigger seed peer download seeds.
-	stream, err := j.resource.SeedPeer().Client().ObtainSeeds(ctx, &cdnsystemv1.SeedRequest{
+	stream, err := client.ObtainSeeds(ctx, &cdnsystemv1.SeedRequest{
 		TaskId:  taskID,
 		Url:     req.URL,
 		UrlMeta: urlMeta,
@@ -343,7 +356,23 @@ func (j *job) preheatV2SingleSeedPeerByURL(ctx context.Context, url string, req
 	filteredQueryParams := idgen.ParseFilteredQueryParams(req.FilteredQueryParams)
 	taskID := idgen.TaskIDV2ByURLBased(url, req.PieceLength, req.Tag, req.Application, filteredQueryParams)
 	advertiseIP := j.config.Server.AdvertiseIP.String()
-	stream, err := j.resource.SeedPeer().Client().DownloadTask(ctx, taskID, &dfdaemonv2.DownloadTaskRequest{
+
+	selected, err := j.resource.SeedPeer().Select(ctx, taskID)
+	if err != nil {
+		return nil, err
+	}
+
+	addr := fmt.Sprintf("%s:%d", selected.IP, selected.Port)
+	log.Infof("[preheat]: selected seed peer %s", addr)
+
+	// TODO(chlins): reuse the client if we encounter the performance issue in future.
+	client, err := dfdaemonclient.GetV2ByAddr(ctx, addr, j.dialOptions...)
+	if err != nil {
+		return nil, err
+	}
+	defer client.Close()
+
+	stream, err := client.DownloadTask(ctx, taskID, &dfdaemonv2.DownloadTaskRequest{
 		Download: &commonv2.Download{
 			Url:                 url,
 			PieceLength:         req.PieceLength,
@@ -410,7 +439,7 @@ func (j *job) PreheatAllSeedPeers(ctx context.Context, req *internaljob.PreheatR
 			for _, seedPeer := range seedPeers {
 				var (
 					hostname = seedPeer.Hostname
-					ip       = seedPeer.Ip
+					ip       = seedPeer.IP
 					port     = seedPeer.Port
 				)
 
@@ -554,20 +583,20 @@ func (j *job) PreheatAllSeedPeers(ctx context.Context, req *internaljob.PreheatR
 // 2. Count: If count is provided, selects up to the specified number of seed peers. If count exceeds the number of available seed peers, all seed peers are selected.
 // 3. Percentage: If percentage is provided, selects a proportional number of seed peers (rounded down). Ensures at least one seed peer is selected if percentage > 0.
 // Priority: IPs > Count > Percentage
-func (j *job) selectSeedPeers(ips []string, count *uint32, percentage *uint32, log *logger.SugaredLoggerOnWith) ([]*managerv2.SeedPeer, error) {
+func (j *job) selectSeedPeers(ips []string, count *uint32, percentage *uint32, log *logger.SugaredLoggerOnWith) ([]*resource.Host, error) {
 	if !j.config.SeedPeer.Enable {
 		return nil, fmt.Errorf("cluster %d scheduler %s has disabled seed peer", j.config.Manager.SchedulerClusterID, j.config.Server.AdvertiseIP)
 	}
 
-	seedPeers := j.resource.SeedPeer().Client().SeedPeers()
+	seedPeers := j.resource.HostManager().LoadAllSeeds()
 	if len(seedPeers) == 0 {
 		return nil, fmt.Errorf("cluster %d scheduler %s has no available seed peer", j.config.Manager.SchedulerClusterID, j.config.Server.AdvertiseIP)
 	}
 
 	if len(ips) > 0 {
-		selectedSeedPeers := make([]*managerv2.SeedPeer, 0, len(ips))
+		selectedSeedPeers := make([]*resource.Host, 0, len(ips))
 		for _, seedPeer := range seedPeers {
-			if slices.Contains(ips, seedPeer.Ip) {
+			if slices.Contains(ips, seedPeer.IP) {
 				selectedSeedPeers = append(selectedSeedPeers, seedPeer)
 				continue
 			}
diff --git a/scheduler/resource/standard/host_manager.go b/scheduler/resource/standard/host_manager.go
index 909a0395..6c480bde 100644
--- a/scheduler/resource/standard/host_manager.go
+++ b/scheduler/resource/standard/host_manager.go
@@ -63,6 +63,9 @@ type HostManager interface {
 	// LoadAllNormals loads all normal hosts through the Range of sync.Map.
 	LoadAllNormals() []*Host
 
+	// LoadAllSeeds loads all seed hosts through the Range of sync.Map.
+	LoadAllSeeds() []*Host
+
 	// Try to reclaim host.
 	RunGC(context.Context) error
 }
@@ -163,6 +166,27 @@ func (h *hostManager) LoadAllNormals() []*Host {
 	return hosts
 }
 
+// LoadAllSeeds loads all seed hosts through the Range of sync.Map.
+func (h *hostManager) LoadAllSeeds() []*Host {
+	hosts := make([]*Host, 0)
+	h.Map.Range(func(key, value any) bool {
+		host, ok := value.(*Host)
+		if !ok {
+			host.Log.Error("invalid host")
+			return true
+		}
+
+		if host.Type == types.HostTypeNormal {
+			return true
+		}
+
+		hosts = append(hosts, host)
+		return true
+	})
+
+	return hosts
+}
+
 // LoadRandom loads host randomly through the Range of sync.Map.
 func (h *hostManager) LoadRandom(n int, blocklist set.SafeSet[string]) []*Host {
 	hosts := make([]*Host, 0, n)
diff --git a/scheduler/resource/standard/host_manager_mock.go b/scheduler/resource/standard/host_manager_mock.go
index bb6b7e3d..7b08c2d8 100644
--- a/scheduler/resource/standard/host_manager_mock.go
+++ b/scheduler/resource/standard/host_manager_mock.go
@@ -96,6 +96,20 @@ func (mr *MockHostManagerMockRecorder) LoadAllNormals() *gomock.Call {
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadAllNormals", reflect.TypeOf((*MockHostManager)(nil).LoadAllNormals))
 }
 
+// LoadAllSeeds mocks base method.
+func (m *MockHostManager) LoadAllSeeds() []*Host {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "LoadAllSeeds")
+	ret0, _ := ret[0].([]*Host)
+	return ret0
+}
+
+// LoadAllSeeds indicates an expected call of LoadAllSeeds.
+func (mr *MockHostManagerMockRecorder) LoadAllSeeds() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadAllSeeds", reflect.TypeOf((*MockHostManager)(nil).LoadAllSeeds))
+}
+
 // LoadOrStore mocks base method.
 func (m *MockHostManager) LoadOrStore(arg0 *Host) (*Host, bool) {
 	m.ctrl.T.Helper()
diff --git a/scheduler/resource/standard/resource.go b/scheduler/resource/standard/resource.go
index daea1bf6..87676458 100644
--- a/scheduler/resource/standard/resource.go
+++ b/scheduler/resource/standard/resource.go
@@ -41,6 +41,9 @@ type Resource interface {
 	// Task manager interface.
 	TaskManager() TaskManager
 
+	// Serve serves resource service.
+	Serve() error
+
 	// Stop resource service.
 	Stop() error
 }
@@ -64,7 +67,7 @@ type resource struct {
 }
 
 // New returns Resource interface.
-func New(cfg *config.Config, gc gc.GC, dynconfig config.DynconfigInterface, transportCredentials credentials.TransportCredentials) (Resource, error) {
+func New(cfg *config.Config, gc gc.GC, transportCredentials credentials.TransportCredentials) (Resource, error) {
 	resource := &resource{config: cfg}
 
 	// Initialize host manager interface.
@@ -91,12 +94,7 @@ func New(cfg *config.Config, gc gc.GC, dynconfig config.DynconfigInterface, tran
 	// Initialize seed peer interface.
 	if cfg.SeedPeer.Enable {
 		dialOptions := []grpc.DialOption{grpc.WithStatsHandler(otelgrpc.NewClientHandler()), grpc.WithTransportCredentials(transportCredentials)}
-		client, err := newSeedPeerClient(dynconfig, hostManager, dialOptions...)
-		if err != nil {
-			return nil, err
-		}
-
-		resource.seedPeer = newSeedPeer(client, peerManager, hostManager)
+		resource.seedPeer = newSeedPeer(peerManager, hostManager, dialOptions...)
 	}
 
 	return resource, nil
@@ -122,6 +120,15 @@ func (r *resource) TaskManager() TaskManager {
 	return r.taskManager
 }
 
+// Serve serves resource service.
+func (r *resource) Serve() error {
+	if r.config.SeedPeer.Enable {
+		return r.seedPeer.Serve()
+	}
+
+	return nil
+}
+
 // Stop resource service.
 func (r *resource) Stop() error {
 	if r.config.SeedPeer.Enable {
diff --git a/scheduler/resource/standard/resource_mock.go b/scheduler/resource/standard/resource_mock.go
index 51519879..b53360ac 100644
--- a/scheduler/resource/standard/resource_mock.go
+++ b/scheduler/resource/standard/resource_mock.go
@@ -81,6 +81,20 @@ func (mr *MockResourceMockRecorder) SeedPeer() *gomock.Call {
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SeedPeer", reflect.TypeOf((*MockResource)(nil).SeedPeer))
 }
 
+// Serve mocks base method.
+func (m *MockResource) Serve() error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Serve")
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// Serve indicates an expected call of Serve.
+func (mr *MockResourceMockRecorder) Serve() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Serve", reflect.TypeOf((*MockResource)(nil).Serve))
+}
+
 // Stop mocks base method.
 func (m *MockResource) Stop() error {
 	m.ctrl.T.Helper()
diff --git a/scheduler/resource/standard/seed_peer.go b/scheduler/resource/standard/seed_peer.go
index 18e8d820..db73926f 100644
--- a/scheduler/resource/standard/seed_peer.go
+++ b/scheduler/resource/standard/seed_peer.go
@@ -22,9 +22,12 @@ import (
 	"context"
 	"fmt"
 	"io"
+	"sync"
 	"time"
 
 	"go.opentelemetry.io/otel/trace"
+	"google.golang.org/grpc"
+	"stathat.com/c/consistent"
 
 	cdnsystemv1 "d7y.io/api/v2/pkg/apis/cdnsystem/v1"
 	commonv1 "d7y.io/api/v2/pkg/apis/common/v1"
@@ -32,16 +35,24 @@ import (
 	dfdaemonv2 "d7y.io/api/v2/pkg/apis/dfdaemon/v2"
 	schedulerv1 "d7y.io/api/v2/pkg/apis/scheduler/v1"
 
+	logger "d7y.io/dragonfly/v2/internal/dflog"
+	"d7y.io/dragonfly/v2/pkg/dfnet"
 	"d7y.io/dragonfly/v2/pkg/digest"
 	"d7y.io/dragonfly/v2/pkg/idgen"
 	"d7y.io/dragonfly/v2/pkg/net/http"
+	cndsystemclient "d7y.io/dragonfly/v2/pkg/rpc/cdnsystem/client"
 	"d7y.io/dragonfly/v2/pkg/rpc/common"
+	dfdaemonclient "d7y.io/dragonfly/v2/pkg/rpc/dfdaemon/client"
+	healthclient "d7y.io/dragonfly/v2/pkg/rpc/health/client"
 	"d7y.io/dragonfly/v2/scheduler/metrics"
 )
 
 const (
 	// Default value of seed peer failed timeout.
 	SeedPeerFailedTimeout = 30 * time.Minute
+
+	// SeedPeerRefreshInterval is the interval of refreshing seed peers.
+	SeedPeerRefreshInterval = 1 * time.Minute
 )
 
 // SeedPeer is the interface used for seed peer.
@@ -54,8 +65,11 @@ type SeedPeer interface {
 	// Used only in v1 version of the grpc.
 	TriggerTask(context.Context, *http.Range, *Task) (*Peer, *schedulerv1.PeerResult, error)
 
-	// Client returns grpc client of seed peer.
-	Client() SeedPeerClient
+	// Select selects a seed peer target by the task id.
+	Select(context.Context, string) (*Host, error)
+
+	// Serve serves the seed peer service.
+	Serve() error
 
 	// Stop seed peer service.
 	Stop() error
@@ -63,22 +77,34 @@ type SeedPeer interface {
 
 // seedPeer contains content for seed peer.
 type seedPeer struct {
-	// client is the dynamic client of seed peer.
-	client SeedPeerClient
-
 	// peerManager is PeerManager interface.
 	peerManager PeerManager
 
 	// hostManager is HostManager interface.
 	hostManager HostManager
+
+	// dialOpts is the options for grpc dial.
+	dialOptions []grpc.DialOption
+
+	// hosts is the list of seed peers.
+	hosts *sync.Map
+
+	// hashring is the hashring constructed from seed peers.
+	hashring *consistent.Consistent
+
+	// done is the channel to stop the seed peer service.
+	done chan struct{}
 }
 
 // New SeedPeer interface.
-func newSeedPeer(client SeedPeerClient, peerManager PeerManager, hostManager HostManager) SeedPeer {
+func newSeedPeer(peerManager PeerManager, hostManager HostManager, dialOptions ...grpc.DialOption) SeedPeer {
 	return &seedPeer{
-		client:      client,
 		peerManager: peerManager,
 		hostManager: hostManager,
+		dialOptions: dialOptions,
+		hosts:       &sync.Map{},
+		hashring:    consistent.New(),
+		done:        make(chan struct{}),
 	}
 }
 
@@ -88,7 +114,22 @@ func (s *seedPeer) TriggerDownloadTask(ctx context.Context, taskID string, req *
 	ctx, cancel := context.WithCancel(trace.ContextWithSpan(ctx, trace.SpanFromContext(ctx)))
 	defer cancel()
 
-	stream, err := s.client.DownloadTask(ctx, taskID, req)
+	selected, err := s.Select(ctx, taskID)
+	if err != nil {
+		return err
+	}
+
+	addr := fmt.Sprintf("%s:%d", selected.IP, selected.Port)
+	logger.Infof("selected seed peer %s for task %s", addr, taskID)
+
+	// TODO(chlins): reuse the client if we encounter the performance issue in future.
+	client, err := dfdaemonclient.GetV2ByAddr(ctx, addr, s.dialOptions...)
+	if err != nil {
+		return err
+	}
+	defer client.Close()
+
+	stream, err := client.DownloadTask(ctx, taskID, req)
 	if err != nil {
 		return err
 	}
@@ -125,7 +166,22 @@ func (s *seedPeer) TriggerTask(ctx context.Context, rg *http.Range, task *Task)
 		urlMeta.Range = rg.URLMetaString()
 	}
 
-	stream, err := s.client.ObtainSeeds(ctx, &cdnsystemv1.SeedRequest{
+	selected, err := s.Select(ctx, task.ID)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	addr := fmt.Sprintf("%s:%d", selected.IP, selected.Port)
+	logger.Infof("selected seed peer %s for task %s", addr, task.ID)
+
+	// TODO(chlins): reuse the client if we encounter the performance issue in future.
+	client, err := cndsystemclient.GetClientByAddr(ctx, dfnet.NetAddr{Type: dfnet.TCP, Addr: addr}, s.dialOptions...)
+	if err != nil {
+		return nil, nil, err
+	}
+	defer client.Close()
+
+	stream, err := client.ObtainSeeds(ctx, &cdnsystemv1.SeedRequest{
 		TaskId:  task.ID,
 		Url:     task.URL,
 		UrlMeta: urlMeta,
@@ -221,6 +277,26 @@ func (s *seedPeer) TriggerTask(ctx context.Context, rg *http.Range, task *Task)
 	}
 }
 
+// Select selects a seed peer by the task id.
+func (s *seedPeer) Select(ctx context.Context, taskID string) (*Host, error) {
+	// The synchronization of the hash ring is handled by the refreshSeedPeers periodically and asynchronously.
+	if len(s.hashring.Members()) == 0 {
+		return nil, fmt.Errorf("no seed peer available")
+	}
+
+	addr, err := s.hashring.Get(taskID)
+	if err != nil {
+		return nil, fmt.Errorf("failed to select seed peer: %w", err)
+	}
+
+	host, ok := s.hosts.Load(addr)
+	if !ok {
+		return nil, fmt.Errorf("failed to load host: %s", addr)
+	}
+
+	return host.(*Host), nil
+}
+
 // Initialize seed peer.
 func (s *seedPeer) initSeedPeer(ctx context.Context, rg *http.Range, task *Task, hostID string, peerID string) (*Peer, error) {
 	// Load host from manager.
@@ -255,12 +331,54 @@ func (s *seedPeer) initSeedPeer(ctx context.Context, rg *http.Range, task *Task,
 	return peer, nil
 }
 
-// Client is seed peer grpc client.
-func (s *seedPeer) Client() SeedPeerClient {
-	return s.client
+func (s *seedPeer) refresh(ctx context.Context) error {
+	hosts := s.hostManager.LoadAllSeeds()
+	if len(hosts) == 0 {
+		return fmt.Errorf("no seed peer found in host manager")
+	}
+
+	healthyHosts := &sync.Map{}
+	// Do the health check for each seed peer.
+	for _, host := range hosts {
+		addr := fmt.Sprintf("%s:%d", host.IP, host.Port)
+		if err := healthclient.Check(ctx, addr, s.dialOptions...); err != nil {
+			logger.Errorf("failed to check the healthy for seed peer %s: %v", addr, err)
+		} else {
+			healthyHosts.Store(addr, host)
+		}
+	}
+
+	s.hosts = healthyHosts
+
+	hashring := consistent.New()
+	s.hosts.Range(func(addr, _ any) bool {
+		hashring.Add(addr.(string))
+		return true
+	})
+
+	s.hashring = hashring
+	return nil
+}
+
+// Serve serves the seed peer service.
+func (s *seedPeer) Serve() error {
+	ticker := time.NewTicker(SeedPeerRefreshInterval)
+	defer ticker.Stop()
+
+	for {
+		select {
+		case <-ticker.C:
+			if err := s.refresh(context.Background()); err != nil {
+				logger.Errorf("failed to refresh seed peers: %v", err)
+			}
+		case <-s.done:
+			return nil
+		}
+	}
 }
 
 // Stop seed peer service.
 func (s *seedPeer) Stop() error {
-	return s.client.Close()
+	close(s.done)
+	return nil
 }
diff --git a/scheduler/resource/standard/seed_peer_client.go b/scheduler/resource/standard/seed_peer_client.go
deleted file mode 100644
index 027dd98a..00000000
--- a/scheduler/resource/standard/seed_peer_client.go
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- *     Copyright 2022 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 seed_peer_client_mock.go -source seed_peer_client.go -package standard
-
-package standard
-
-import (
-	"context"
-	"fmt"
-	"reflect"
-
-	"github.com/hashicorp/go-multierror"
-	"google.golang.org/grpc"
-
-	managerv2 "d7y.io/api/v2/pkg/apis/manager/v2"
-
-	logger "d7y.io/dragonfly/v2/internal/dflog"
-	"d7y.io/dragonfly/v2/pkg/dfnet"
-	"d7y.io/dragonfly/v2/pkg/idgen"
-	cdnsystemclient "d7y.io/dragonfly/v2/pkg/rpc/cdnsystem/client"
-	dfdaemonclient "d7y.io/dragonfly/v2/pkg/rpc/dfdaemon/client"
-	"d7y.io/dragonfly/v2/pkg/types"
-	"d7y.io/dragonfly/v2/scheduler/config"
-)
-
-// SeedPeerClient is the interface used for client of seed peer.
-type SeedPeerClient interface {
-	// Addrs returns the addresses of seed peers.
-	Addrs() []string
-
-	// SeedPeers returns the seed peers working for the scheduler.
-	SeedPeers() []*managerv2.SeedPeer
-
-	// Client is cdnsystem grpc client interface.
-	cdnsystemclient.Client
-
-	// V2 is dfdaemon v2 grpc client interface.
-	dfdaemonclient.V2
-
-	// Observer is dynconfig observer interface.
-	config.Observer
-}
-
-// seedPeerClient contains content for client of seed peer.
-type seedPeerClient struct {
-	// Client is cdnsystem grpc client interface.
-	cdnsystemclient.Client
-
-	// V2 is dfdaemon v2 grpc client interface.
-	dfdaemonclient.V2
-
-	// hostManager is host manager.
-	hostManager HostManager
-
-	// dynconfig is dynconfig interface.
-	dynconfig config.DynconfigInterface
-
-	// data is dynconfig data.
-	data *config.DynconfigData
-}
-
-// New seed peer client interface.
-func newSeedPeerClient(dynconfig config.DynconfigInterface, hostManager HostManager, opts ...grpc.DialOption) (SeedPeerClient, error) {
-	config, err := dynconfig.Get()
-	if err != nil {
-		return nil, err
-	}
-	logger.Infof("initialize seed peer addresses: %#v", seedPeersToNetAddrs(config.Scheduler.SeedPeers))
-
-	// Initialize seed peer grpc client.
-	cdnsystemClient, err := cdnsystemclient.GetClient(context.Background(), dynconfig, opts...)
-	if err != nil {
-		return nil, err
-	}
-	logger.Infof("cdnsystemClient: %#v", cdnsystemClient)
-
-	// Initialize dfdaemon v2 grpc client.
-	dfdaemonClient, err := dfdaemonclient.GetV2(context.Background(), dynconfig, opts...)
-	if err != nil {
-		return nil, err
-	}
-
-	logger.Infof("dfdaemonClient: %#v", dfdaemonClient)
-
-	sc := &seedPeerClient{
-		hostManager: hostManager,
-		Client:      cdnsystemClient,
-		V2:          dfdaemonClient,
-		dynconfig:   dynconfig,
-		data:        config,
-	}
-
-	// Initialize seed peers for host manager.
-	sc.updateSeedPeersForHostManager(config.Scheduler.SeedPeers)
-
-	dynconfig.Register(sc)
-	return sc, nil
-}
-
-// Close closes the seed peer client.
-func (sc *seedPeerClient) Close() error {
-	var errs error
-	if err := sc.Client.Close(); err != nil {
-		errs = multierror.Append(errs, err)
-	}
-
-	if err := sc.V2.Close(); err != nil {
-		errs = multierror.Append(errs, err)
-	}
-
-	return errs
-}
-
-// Addrs returns the addresses of seed peers.
-func (sc *seedPeerClient) Addrs() []string {
-	var addrs []string
-	for _, seedPeer := range sc.data.Scheduler.SeedPeers {
-		addrs = append(addrs, fmt.Sprintf("%s:%d", seedPeer.Ip, seedPeer.Port))
-	}
-
-	return addrs
-}
-
-// SeedPeers returns the seed peers working for the scheduler.
-func (sc *seedPeerClient) SeedPeers() []*managerv2.SeedPeer {
-	return sc.data.Scheduler.SeedPeers
-}
-
-// Dynamic config notify function.
-func (sc *seedPeerClient) OnNotify(data *config.DynconfigData) {
-	if reflect.DeepEqual(sc.data, data) {
-		return
-	}
-
-	// Update seed peers for host manager.
-	sc.updateSeedPeersForHostManager(data.Scheduler.SeedPeers)
-
-	// Update dynamic data.
-	sc.data = data
-
-	// Update grpc seed peer addresses.
-	logger.Infof("addresses have been updated: %#v", seedPeersToNetAddrs(data.Scheduler.SeedPeers))
-}
-
-// updateSeedPeersForHostManager updates seed peers for host manager.
-func (sc *seedPeerClient) updateSeedPeersForHostManager(seedPeers []*managerv2.SeedPeer) {
-	for _, seedPeer := range seedPeers {
-		var concurrentUploadLimit int32
-		if config, err := config.GetSeedPeerClusterConfigBySeedPeer(seedPeer); err == nil {
-			concurrentUploadLimit = int32(config.LoadLimit)
-		}
-
-		id := idgen.HostIDV2(seedPeer.Ip, seedPeer.Hostname, true)
-		seedPeerHost, loaded := sc.hostManager.Load(id)
-		if !loaded {
-			options := []HostOption{WithNetwork(Network{
-				Location: seedPeer.GetLocation(),
-				IDC:      seedPeer.GetIdc(),
-			})}
-
-			if concurrentUploadLimit > 0 {
-				options = append(options, WithConcurrentUploadLimit(concurrentUploadLimit))
-			}
-
-			host := NewHost(
-				id, seedPeer.Ip, seedPeer.Hostname,
-				seedPeer.Port, seedPeer.DownloadPort, types.HostTypeSuperSeed,
-				options...,
-			)
-
-			sc.hostManager.Store(host)
-			continue
-		}
-
-		seedPeerHost.Type = types.HostTypeSuperSeed
-		seedPeerHost.Port = seedPeer.Port
-		seedPeerHost.DownloadPort = seedPeer.DownloadPort
-		seedPeerHost.Network.Location = seedPeer.GetLocation()
-		seedPeerHost.Network.IDC = seedPeer.GetIdc()
-
-		if concurrentUploadLimit > 0 {
-			seedPeerHost.ConcurrentUploadLimit.Store(concurrentUploadLimit)
-		}
-	}
-
-	return
-}
-
-// seedPeersToNetAddrs coverts []*config.SeedPeer to []dfnet.NetAddr.
-func seedPeersToNetAddrs(seedPeers []*managerv2.SeedPeer) []dfnet.NetAddr {
-	netAddrs := make([]dfnet.NetAddr, 0, len(seedPeers))
-	for _, seedPeer := range seedPeers {
-		netAddrs = append(netAddrs, dfnet.NetAddr{
-			Type: dfnet.TCP,
-			Addr: fmt.Sprintf("%s:%d", seedPeer.Ip, seedPeer.Port),
-		})
-	}
-
-	return netAddrs
-}
diff --git a/scheduler/resource/standard/seed_peer_client_mock.go b/scheduler/resource/standard/seed_peer_client_mock.go
deleted file mode 100644
index 91ed94c1..00000000
--- a/scheduler/resource/standard/seed_peer_client_mock.go
+++ /dev/null
@@ -1,339 +0,0 @@
-// Code generated by MockGen. DO NOT EDIT.
-// Source: seed_peer_client.go
-//
-// Generated by this command:
-//
-//	mockgen -destination seed_peer_client_mock.go -source seed_peer_client.go -package standard
-//
-
-// Package standard is a generated GoMock package.
-package standard
-
-import (
-	context "context"
-	reflect "reflect"
-
-	cdnsystem "d7y.io/api/v2/pkg/apis/cdnsystem/v1"
-	common "d7y.io/api/v2/pkg/apis/common/v1"
-	common0 "d7y.io/api/v2/pkg/apis/common/v2"
-	dfdaemon "d7y.io/api/v2/pkg/apis/dfdaemon/v2"
-	manager "d7y.io/api/v2/pkg/apis/manager/v2"
-	config "d7y.io/dragonfly/v2/scheduler/config"
-	gomock "go.uber.org/mock/gomock"
-	grpc "google.golang.org/grpc"
-)
-
-// MockSeedPeerClient is a mock of SeedPeerClient interface.
-type MockSeedPeerClient struct {
-	ctrl     *gomock.Controller
-	recorder *MockSeedPeerClientMockRecorder
-	isgomock struct{}
-}
-
-// MockSeedPeerClientMockRecorder is the mock recorder for MockSeedPeerClient.
-type MockSeedPeerClientMockRecorder struct {
-	mock *MockSeedPeerClient
-}
-
-// NewMockSeedPeerClient creates a new mock instance.
-func NewMockSeedPeerClient(ctrl *gomock.Controller) *MockSeedPeerClient {
-	mock := &MockSeedPeerClient{ctrl: ctrl}
-	mock.recorder = &MockSeedPeerClientMockRecorder{mock}
-	return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use.
-func (m *MockSeedPeerClient) EXPECT() *MockSeedPeerClientMockRecorder {
-	return m.recorder
-}
-
-// Addrs mocks base method.
-func (m *MockSeedPeerClient) Addrs() []string {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "Addrs")
-	ret0, _ := ret[0].([]string)
-	return ret0
-}
-
-// Addrs indicates an expected call of Addrs.
-func (mr *MockSeedPeerClientMockRecorder) Addrs() *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Addrs", reflect.TypeOf((*MockSeedPeerClient)(nil).Addrs))
-}
-
-// Close mocks base method.
-func (m *MockSeedPeerClient) Close() error {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "Close")
-	ret0, _ := ret[0].(error)
-	return ret0
-}
-
-// Close indicates an expected call of Close.
-func (mr *MockSeedPeerClientMockRecorder) Close() *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockSeedPeerClient)(nil).Close))
-}
-
-// DeletePersistentCacheTask mocks base method.
-func (m *MockSeedPeerClient) DeletePersistentCacheTask(arg0 context.Context, arg1 *dfdaemon.DeletePersistentCacheTaskRequest, arg2 ...grpc.CallOption) error {
-	m.ctrl.T.Helper()
-	varargs := []any{arg0, arg1}
-	for _, a := range arg2 {
-		varargs = append(varargs, a)
-	}
-	ret := m.ctrl.Call(m, "DeletePersistentCacheTask", varargs...)
-	ret0, _ := ret[0].(error)
-	return ret0
-}
-
-// DeletePersistentCacheTask indicates an expected call of DeletePersistentCacheTask.
-func (mr *MockSeedPeerClientMockRecorder) DeletePersistentCacheTask(arg0, arg1 any, arg2 ...any) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	varargs := append([]any{arg0, arg1}, arg2...)
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePersistentCacheTask", reflect.TypeOf((*MockSeedPeerClient)(nil).DeletePersistentCacheTask), varargs...)
-}
-
-// DeleteTask mocks base method.
-func (m *MockSeedPeerClient) DeleteTask(arg0 context.Context, arg1 *dfdaemon.DeleteTaskRequest, arg2 ...grpc.CallOption) error {
-	m.ctrl.T.Helper()
-	varargs := []any{arg0, arg1}
-	for _, a := range arg2 {
-		varargs = append(varargs, a)
-	}
-	ret := m.ctrl.Call(m, "DeleteTask", varargs...)
-	ret0, _ := ret[0].(error)
-	return ret0
-}
-
-// DeleteTask indicates an expected call of DeleteTask.
-func (mr *MockSeedPeerClientMockRecorder) DeleteTask(arg0, arg1 any, arg2 ...any) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	varargs := append([]any{arg0, arg1}, arg2...)
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteTask", reflect.TypeOf((*MockSeedPeerClient)(nil).DeleteTask), varargs...)
-}
-
-// DownloadPersistentCacheTask mocks base method.
-func (m *MockSeedPeerClient) DownloadPersistentCacheTask(arg0 context.Context, arg1 *dfdaemon.DownloadPersistentCacheTaskRequest, arg2 ...grpc.CallOption) (dfdaemon.DfdaemonUpload_DownloadPersistentCacheTaskClient, error) {
-	m.ctrl.T.Helper()
-	varargs := []any{arg0, arg1}
-	for _, a := range arg2 {
-		varargs = append(varargs, a)
-	}
-	ret := m.ctrl.Call(m, "DownloadPersistentCacheTask", varargs...)
-	ret0, _ := ret[0].(dfdaemon.DfdaemonUpload_DownloadPersistentCacheTaskClient)
-	ret1, _ := ret[1].(error)
-	return ret0, ret1
-}
-
-// DownloadPersistentCacheTask indicates an expected call of DownloadPersistentCacheTask.
-func (mr *MockSeedPeerClientMockRecorder) DownloadPersistentCacheTask(arg0, arg1 any, arg2 ...any) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	varargs := append([]any{arg0, arg1}, arg2...)
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DownloadPersistentCacheTask", reflect.TypeOf((*MockSeedPeerClient)(nil).DownloadPersistentCacheTask), varargs...)
-}
-
-// DownloadPiece mocks base method.
-func (m *MockSeedPeerClient) DownloadPiece(arg0 context.Context, arg1 *dfdaemon.DownloadPieceRequest, arg2 ...grpc.CallOption) (*dfdaemon.DownloadPieceResponse, error) {
-	m.ctrl.T.Helper()
-	varargs := []any{arg0, arg1}
-	for _, a := range arg2 {
-		varargs = append(varargs, a)
-	}
-	ret := m.ctrl.Call(m, "DownloadPiece", varargs...)
-	ret0, _ := ret[0].(*dfdaemon.DownloadPieceResponse)
-	ret1, _ := ret[1].(error)
-	return ret0, ret1
-}
-
-// DownloadPiece indicates an expected call of DownloadPiece.
-func (mr *MockSeedPeerClientMockRecorder) DownloadPiece(arg0, arg1 any, arg2 ...any) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	varargs := append([]any{arg0, arg1}, arg2...)
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DownloadPiece", reflect.TypeOf((*MockSeedPeerClient)(nil).DownloadPiece), varargs...)
-}
-
-// DownloadTask mocks base method.
-func (m *MockSeedPeerClient) DownloadTask(arg0 context.Context, arg1 string, arg2 *dfdaemon.DownloadTaskRequest, arg3 ...grpc.CallOption) (dfdaemon.DfdaemonUpload_DownloadTaskClient, error) {
-	m.ctrl.T.Helper()
-	varargs := []any{arg0, arg1, arg2}
-	for _, a := range arg3 {
-		varargs = append(varargs, a)
-	}
-	ret := m.ctrl.Call(m, "DownloadTask", varargs...)
-	ret0, _ := ret[0].(dfdaemon.DfdaemonUpload_DownloadTaskClient)
-	ret1, _ := ret[1].(error)
-	return ret0, ret1
-}
-
-// DownloadTask indicates an expected call of DownloadTask.
-func (mr *MockSeedPeerClientMockRecorder) DownloadTask(arg0, arg1, arg2 any, arg3 ...any) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	varargs := append([]any{arg0, arg1, arg2}, arg3...)
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DownloadTask", reflect.TypeOf((*MockSeedPeerClient)(nil).DownloadTask), varargs...)
-}
-
-// GetPieceTasks mocks base method.
-func (m *MockSeedPeerClient) GetPieceTasks(arg0 context.Context, arg1 *common.PieceTaskRequest, arg2 ...grpc.CallOption) (*common.PiecePacket, error) {
-	m.ctrl.T.Helper()
-	varargs := []any{arg0, arg1}
-	for _, a := range arg2 {
-		varargs = append(varargs, a)
-	}
-	ret := m.ctrl.Call(m, "GetPieceTasks", varargs...)
-	ret0, _ := ret[0].(*common.PiecePacket)
-	ret1, _ := ret[1].(error)
-	return ret0, ret1
-}
-
-// GetPieceTasks indicates an expected call of GetPieceTasks.
-func (mr *MockSeedPeerClientMockRecorder) GetPieceTasks(arg0, arg1 any, arg2 ...any) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	varargs := append([]any{arg0, arg1}, arg2...)
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPieceTasks", reflect.TypeOf((*MockSeedPeerClient)(nil).GetPieceTasks), varargs...)
-}
-
-// ObtainSeeds mocks base method.
-func (m *MockSeedPeerClient) ObtainSeeds(arg0 context.Context, arg1 *cdnsystem.SeedRequest, arg2 ...grpc.CallOption) (cdnsystem.Seeder_ObtainSeedsClient, error) {
-	m.ctrl.T.Helper()
-	varargs := []any{arg0, arg1}
-	for _, a := range arg2 {
-		varargs = append(varargs, a)
-	}
-	ret := m.ctrl.Call(m, "ObtainSeeds", varargs...)
-	ret0, _ := ret[0].(cdnsystem.Seeder_ObtainSeedsClient)
-	ret1, _ := ret[1].(error)
-	return ret0, ret1
-}
-
-// ObtainSeeds indicates an expected call of ObtainSeeds.
-func (mr *MockSeedPeerClientMockRecorder) ObtainSeeds(arg0, arg1 any, arg2 ...any) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	varargs := append([]any{arg0, arg1}, arg2...)
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ObtainSeeds", reflect.TypeOf((*MockSeedPeerClient)(nil).ObtainSeeds), varargs...)
-}
-
-// OnNotify mocks base method.
-func (m *MockSeedPeerClient) OnNotify(arg0 *config.DynconfigData) {
-	m.ctrl.T.Helper()
-	m.ctrl.Call(m, "OnNotify", arg0)
-}
-
-// OnNotify indicates an expected call of OnNotify.
-func (mr *MockSeedPeerClientMockRecorder) OnNotify(arg0 any) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnNotify", reflect.TypeOf((*MockSeedPeerClient)(nil).OnNotify), arg0)
-}
-
-// SeedPeers mocks base method.
-func (m *MockSeedPeerClient) SeedPeers() []*manager.SeedPeer {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "SeedPeers")
-	ret0, _ := ret[0].([]*manager.SeedPeer)
-	return ret0
-}
-
-// SeedPeers indicates an expected call of SeedPeers.
-func (mr *MockSeedPeerClientMockRecorder) SeedPeers() *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SeedPeers", reflect.TypeOf((*MockSeedPeerClient)(nil).SeedPeers))
-}
-
-// StatPersistentCacheTask mocks base method.
-func (m *MockSeedPeerClient) StatPersistentCacheTask(arg0 context.Context, arg1 *dfdaemon.StatPersistentCacheTaskRequest, arg2 ...grpc.CallOption) (*common0.PersistentCacheTask, error) {
-	m.ctrl.T.Helper()
-	varargs := []any{arg0, arg1}
-	for _, a := range arg2 {
-		varargs = append(varargs, a)
-	}
-	ret := m.ctrl.Call(m, "StatPersistentCacheTask", varargs...)
-	ret0, _ := ret[0].(*common0.PersistentCacheTask)
-	ret1, _ := ret[1].(error)
-	return ret0, ret1
-}
-
-// StatPersistentCacheTask indicates an expected call of StatPersistentCacheTask.
-func (mr *MockSeedPeerClientMockRecorder) StatPersistentCacheTask(arg0, arg1 any, arg2 ...any) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	varargs := append([]any{arg0, arg1}, arg2...)
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StatPersistentCacheTask", reflect.TypeOf((*MockSeedPeerClient)(nil).StatPersistentCacheTask), varargs...)
-}
-
-// StatTask mocks base method.
-func (m *MockSeedPeerClient) StatTask(arg0 context.Context, arg1 *dfdaemon.StatTaskRequest, arg2 ...grpc.CallOption) (*common0.Task, error) {
-	m.ctrl.T.Helper()
-	varargs := []any{arg0, arg1}
-	for _, a := range arg2 {
-		varargs = append(varargs, a)
-	}
-	ret := m.ctrl.Call(m, "StatTask", varargs...)
-	ret0, _ := ret[0].(*common0.Task)
-	ret1, _ := ret[1].(error)
-	return ret0, ret1
-}
-
-// StatTask indicates an expected call of StatTask.
-func (mr *MockSeedPeerClientMockRecorder) StatTask(arg0, arg1 any, arg2 ...any) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	varargs := append([]any{arg0, arg1}, arg2...)
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StatTask", reflect.TypeOf((*MockSeedPeerClient)(nil).StatTask), varargs...)
-}
-
-// SyncPieceTasks mocks base method.
-func (m *MockSeedPeerClient) SyncPieceTasks(arg0 context.Context, arg1 *common.PieceTaskRequest, arg2 ...grpc.CallOption) (cdnsystem.Seeder_SyncPieceTasksClient, error) {
-	m.ctrl.T.Helper()
-	varargs := []any{arg0, arg1}
-	for _, a := range arg2 {
-		varargs = append(varargs, a)
-	}
-	ret := m.ctrl.Call(m, "SyncPieceTasks", varargs...)
-	ret0, _ := ret[0].(cdnsystem.Seeder_SyncPieceTasksClient)
-	ret1, _ := ret[1].(error)
-	return ret0, ret1
-}
-
-// SyncPieceTasks indicates an expected call of SyncPieceTasks.
-func (mr *MockSeedPeerClientMockRecorder) SyncPieceTasks(arg0, arg1 any, arg2 ...any) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	varargs := append([]any{arg0, arg1}, arg2...)
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyncPieceTasks", reflect.TypeOf((*MockSeedPeerClient)(nil).SyncPieceTasks), varargs...)
-}
-
-// SyncPieces mocks base method.
-func (m *MockSeedPeerClient) SyncPieces(arg0 context.Context, arg1 *dfdaemon.SyncPiecesRequest, arg2 ...grpc.CallOption) (dfdaemon.DfdaemonUpload_SyncPiecesClient, error) {
-	m.ctrl.T.Helper()
-	varargs := []any{arg0, arg1}
-	for _, a := range arg2 {
-		varargs = append(varargs, a)
-	}
-	ret := m.ctrl.Call(m, "SyncPieces", varargs...)
-	ret0, _ := ret[0].(dfdaemon.DfdaemonUpload_SyncPiecesClient)
-	ret1, _ := ret[1].(error)
-	return ret0, ret1
-}
-
-// SyncPieces indicates an expected call of SyncPieces.
-func (mr *MockSeedPeerClientMockRecorder) SyncPieces(arg0, arg1 any, arg2 ...any) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	varargs := append([]any{arg0, arg1}, arg2...)
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyncPieces", reflect.TypeOf((*MockSeedPeerClient)(nil).SyncPieces), varargs...)
-}
-
-// UpdatePersistentCacheTask mocks base method.
-func (m *MockSeedPeerClient) UpdatePersistentCacheTask(arg0 context.Context, arg1 *dfdaemon.UpdatePersistentCacheTaskRequest, arg2 ...grpc.CallOption) error {
-	m.ctrl.T.Helper()
-	varargs := []any{arg0, arg1}
-	for _, a := range arg2 {
-		varargs = append(varargs, a)
-	}
-	ret := m.ctrl.Call(m, "UpdatePersistentCacheTask", varargs...)
-	ret0, _ := ret[0].(error)
-	return ret0
-}
-
-// UpdatePersistentCacheTask indicates an expected call of UpdatePersistentCacheTask.
-func (mr *MockSeedPeerClientMockRecorder) UpdatePersistentCacheTask(arg0, arg1 any, arg2 ...any) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	varargs := append([]any{arg0, arg1}, arg2...)
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdatePersistentCacheTask", reflect.TypeOf((*MockSeedPeerClient)(nil).UpdatePersistentCacheTask), varargs...)
-}
diff --git a/scheduler/resource/standard/seed_peer_mock.go b/scheduler/resource/standard/seed_peer_mock.go
index 9dbd5c25..21edf332 100644
--- a/scheduler/resource/standard/seed_peer_mock.go
+++ b/scheduler/resource/standard/seed_peer_mock.go
@@ -43,18 +43,33 @@ func (m *MockSeedPeer) EXPECT() *MockSeedPeerMockRecorder {
 	return m.recorder
 }
 
-// Client mocks base method.
-func (m *MockSeedPeer) Client() SeedPeerClient {
+// Select mocks base method.
+func (m *MockSeedPeer) Select(arg0 context.Context, arg1 string) (*Host, error) {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "Client")
-	ret0, _ := ret[0].(SeedPeerClient)
+	ret := m.ctrl.Call(m, "Select", arg0, arg1)
+	ret0, _ := ret[0].(*Host)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// Select indicates an expected call of Select.
+func (mr *MockSeedPeerMockRecorder) Select(arg0, arg1 any) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Select", reflect.TypeOf((*MockSeedPeer)(nil).Select), arg0, arg1)
+}
+
+// Serve mocks base method.
+func (m *MockSeedPeer) Serve() error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Serve")
+	ret0, _ := ret[0].(error)
 	return ret0
 }
 
-// Client indicates an expected call of Client.
-func (mr *MockSeedPeerMockRecorder) Client() *gomock.Call {
+// Serve indicates an expected call of Serve.
+func (mr *MockSeedPeerMockRecorder) Serve() *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Client", reflect.TypeOf((*MockSeedPeer)(nil).Client))
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Serve", reflect.TypeOf((*MockSeedPeer)(nil).Serve))
 }
 
 // Stop mocks base method.
diff --git a/scheduler/scheduler.go b/scheduler/scheduler.go
index 5422eadf..2ce4ab54 100644
--- a/scheduler/scheduler.go
+++ b/scheduler/scheduler.go
@@ -161,7 +161,7 @@ func New(ctx context.Context, cfg *config.Config, d dfpath.Dfpath) (*Server, err
 	s.dynconfig = dynconfig
 
 	// Initialize resource.
-	resource, err := standard.New(cfg, s.gc, dynconfig, seedPeerClientTransportCredentials)
+	resource, err := standard.New(cfg, s.gc, seedPeerClientTransportCredentials)
 	if err != nil {
 		return nil, err
 	}
@@ -188,7 +188,11 @@ func New(ctx context.Context, cfg *config.Config, d dfpath.Dfpath) (*Server, err
 
 	// Initialize job service.
 	if cfg.Job.Enable && rdb != nil {
-		s.job, err = job.New(cfg, resource)
+		dialOptions := []grpc.DialOption{
+			grpc.WithStatsHandler(otelgrpc.NewClientHandler()),
+			grpc.WithTransportCredentials(seedPeerClientTransportCredentials),
+		}
+		s.job, err = job.New(cfg, resource, dialOptions...)
 		if err != nil {
 			return nil, err
 		}
@@ -265,6 +269,15 @@ func (s *Server) Serve() error {
 		logger.Info("announcer start successfully")
 	}()
 
+	// Serve resource.
+	go func() {
+		if err := s.resource.Serve(); err != nil {
+			logger.Fatalf("resource start failed: %s", err.Error())
+		}
+
+		logger.Info("resource start successfully")
+	}()
+
 	// Generate GRPC listener.
 	ip, ok := ip.FormatIP(s.config.Server.ListenIP.String())
 	if !ok {



Test patch:
diff --git a/scheduler/resource/standard/resource_test.go b/scheduler/resource/standard/resource_test.go
index 035ba64e..bc87168b 100644
--- a/scheduler/resource/standard/resource_test.go
+++ b/scheduler/resource/standard/resource_test.go
@@ -23,43 +23,25 @@ import (
 
 	"github.com/stretchr/testify/assert"
 	"go.uber.org/mock/gomock"
-	"google.golang.org/grpc/resolver"
-
-	managerv2 "d7y.io/api/v2/pkg/apis/manager/v2"
 
 	"d7y.io/dragonfly/v2/pkg/gc"
 	"d7y.io/dragonfly/v2/pkg/rpc"
 	"d7y.io/dragonfly/v2/scheduler/config"
-	configmocks "d7y.io/dragonfly/v2/scheduler/config/mocks"
 )
 
 func TestResource_New(t *testing.T) {
 	tests := []struct {
 		name   string
 		config *config.Config
-		mock   func(mg *gc.MockGCMockRecorder, md *configmocks.MockDynconfigInterfaceMockRecorder)
+		mock   func(mg *gc.MockGCMockRecorder)
 		expect func(t *testing.T, resource Resource, err error)
 	}{
 		{
 			name:   "new resource",
 			config: config.New(),
-			mock: func(mg *gc.MockGCMockRecorder, md *configmocks.MockDynconfigInterfaceMockRecorder) {
+			mock: func(mg *gc.MockGCMockRecorder) {
 				gomock.InOrder(
 					mg.Add(gomock.Any()).Return(nil).Times(3),
-					md.Get().Return(&config.DynconfigData{
-						Scheduler: &managerv2.Scheduler{
-							SeedPeers: []*managerv2.SeedPeer{
-								{
-									Id: 1,
-								},
-							},
-						},
-					}, nil).Times(1),
-					md.Register(gomock.Any()).Return().Times(1),
-					md.GetResolveSeedPeerAddrs().Return([]resolver.Address{}, nil).Times(1),
-					md.Register(gomock.Any()).Return().Times(1),
-					md.GetResolveSeedPeerAddrs().Return([]resolver.Address{}, nil).Times(1),
-					md.Register(gomock.Any()).Return().Times(1),
 				)
 			},
 			expect: func(t *testing.T, resource Resource, err error) {
@@ -71,7 +53,7 @@ func TestResource_New(t *testing.T) {
 		{
 			name:   "new resource failed because of host manager error",
 			config: config.New(),
-			mock: func(mg *gc.MockGCMockRecorder, md *configmocks.MockDynconfigInterfaceMockRecorder) {
+			mock: func(mg *gc.MockGCMockRecorder) {
 				mg.Add(gomock.Any()).Return(errors.New("foo")).Times(1)
 			},
 			expect: func(t *testing.T, resource Resource, err error) {
@@ -82,7 +64,7 @@ func TestResource_New(t *testing.T) {
 		{
 			name:   "new resource failed because of task manager error",
 			config: config.New(),
-			mock: func(mg *gc.MockGCMockRecorder, md *configmocks.MockDynconfigInterfaceMockRecorder) {
+			mock: func(mg *gc.MockGCMockRecorder) {
 				gomock.InOrder(
 					mg.Add(gomock.Any()).Return(nil).Times(1),
 					mg.Add(gomock.Any()).Return(errors.New("foo")).Times(1),
@@ -96,7 +78,7 @@ func TestResource_New(t *testing.T) {
 		{
 			name:   "new resource failed because of peer manager error",
 			config: config.New(),
-			mock: func(mg *gc.MockGCMockRecorder, md *configmocks.MockDynconfigInterfaceMockRecorder) {
+			mock: func(mg *gc.MockGCMockRecorder) {
 				gomock.InOrder(
 					mg.Add(gomock.Any()).Return(nil).Times(2),
 					mg.Add(gomock.Any()).Return(errors.New("foo")).Times(1),
@@ -107,36 +89,12 @@ func TestResource_New(t *testing.T) {
 				assert.EqualError(err, "foo")
 			},
 		},
-		{
-			name:   "new resource faild because of dynconfig get error",
-			config: config.New(),
-			mock: func(mg *gc.MockGCMockRecorder, md *configmocks.MockDynconfigInterfaceMockRecorder) {
-				gomock.InOrder(
-					mg.Add(gomock.Any()).Return(nil).Times(3),
-					md.Get().Return(&config.DynconfigData{}, errors.New("foo")).Times(1),
-				)
-			},
-			expect: func(t *testing.T, resource Resource, err error) {
-				assert := assert.New(t)
-				assert.EqualError(err, "foo")
-			},
-		},
 		{
 			name:   "new resource faild because of seed peer list is empty",
 			config: config.New(),
-			mock: func(mg *gc.MockGCMockRecorder, md *configmocks.MockDynconfigInterfaceMockRecorder) {
+			mock: func(mg *gc.MockGCMockRecorder) {
 				gomock.InOrder(
 					mg.Add(gomock.Any()).Return(nil).Times(3),
-					md.Get().Return(&config.DynconfigData{
-						Scheduler: &managerv2.Scheduler{
-							SeedPeers: []*managerv2.SeedPeer{},
-						},
-					}, nil).Times(1),
-					md.Register(gomock.Any()).Return().Times(1),
-					md.GetResolveSeedPeerAddrs().Return([]resolver.Address{}, nil).Times(1),
-					md.Register(gomock.Any()).Return().Times(1),
-					md.GetResolveSeedPeerAddrs().Return([]resolver.Address{}, nil).Times(1),
-					md.Register(gomock.Any()).Return().Times(1),
 				)
 			},
 			expect: func(t *testing.T, resource Resource, err error) {
@@ -159,7 +117,7 @@ func TestResource_New(t *testing.T) {
 					Enable: false,
 				},
 			},
-			mock: func(mg *gc.MockGCMockRecorder, md *configmocks.MockDynconfigInterfaceMockRecorder) {
+			mock: func(mg *gc.MockGCMockRecorder) {
 				mg.Add(gomock.Any()).Return(nil).Times(3)
 			},
 			expect: func(t *testing.T, resource Resource, err error) {
@@ -175,10 +133,9 @@ func TestResource_New(t *testing.T) {
 			ctl := gomock.NewController(t)
 			defer ctl.Finish()
 			gc := gc.NewMockGC(ctl)
-			dynconfig := configmocks.NewMockDynconfigInterface(ctl)
-			tc.mock(gc.EXPECT(), dynconfig.EXPECT())
+			tc.mock(gc.EXPECT())
 
-			resource, err := New(tc.config, gc, dynconfig, rpc.NewInsecureCredentials())
+			resource, err := New(tc.config, gc, rpc.NewInsecureCredentials())
 			tc.expect(t, resource, err)
 		})
 	}
diff --git a/scheduler/resource/standard/seed_peer_client_test.go b/scheduler/resource/standard/seed_peer_client_test.go
deleted file mode 100644
index d71328ea..00000000
--- a/scheduler/resource/standard/seed_peer_client_test.go
+++ /dev/null
@@ -1,370 +0,0 @@
-/*
- *     Copyright 2022 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 standard
-
-import (
-	"errors"
-	"fmt"
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-	gomock "go.uber.org/mock/gomock"
-	"google.golang.org/grpc"
-	"google.golang.org/grpc/credentials/insecure"
-	"google.golang.org/grpc/resolver"
-
-	managerv2 "d7y.io/api/v2/pkg/apis/manager/v2"
-
-	"d7y.io/dragonfly/v2/pkg/dfnet"
-	pkgtypes "d7y.io/dragonfly/v2/pkg/types"
-	"d7y.io/dragonfly/v2/scheduler/config"
-	configmocks "d7y.io/dragonfly/v2/scheduler/config/mocks"
-)
-
-func TestSeedPeerClient_newSeedPeerClient(t *testing.T) {
-	tests := []struct {
-		name   string
-		mock   func(dynconfig *configmocks.MockDynconfigInterfaceMockRecorder, hostManager *MockHostManagerMockRecorder)
-		expect func(t *testing.T, err error)
-	}{
-		{
-			name: "new seed peer client",
-			mock: func(dynconfig *configmocks.MockDynconfigInterfaceMockRecorder, hostManager *MockHostManagerMockRecorder) {
-				gomock.InOrder(
-					dynconfig.Get().Return(&config.DynconfigData{
-						Scheduler: &managerv2.Scheduler{
-							SeedPeers: []*managerv2.SeedPeer{{Id: 1}},
-						},
-					}, nil).Times(1),
-					dynconfig.Register(gomock.Any()).Return().Times(1),
-					dynconfig.GetResolveSeedPeerAddrs().Return([]resolver.Address{}, nil).Times(1),
-					dynconfig.Register(gomock.Any()).Return().Times(1),
-					dynconfig.GetResolveSeedPeerAddrs().Return([]resolver.Address{}, nil).Times(1),
-					hostManager.Load(gomock.Any()).Return(nil, false).Times(1),
-					hostManager.Store(gomock.Any()).Return().Times(1),
-					dynconfig.Register(gomock.Any()).Return().Times(1),
-				)
-			},
-			expect: func(t *testing.T, err error) {
-				assert := assert.New(t)
-				assert.NoError(err)
-			},
-		},
-		{
-			name: "new seed peer client failed because of dynconfig get error data",
-			mock: func(dynconfig *configmocks.MockDynconfigInterfaceMockRecorder, hostManager *MockHostManagerMockRecorder) {
-				dynconfig.Get().Return(&config.DynconfigData{}, errors.New("foo")).Times(1)
-			},
-			expect: func(t *testing.T, err error) {
-				assert := assert.New(t)
-				assert.EqualError(err, "foo")
-			},
-		},
-		{
-			name: "new seed peer client failed because of seed peer list is empty",
-			mock: func(dynconfig *configmocks.MockDynconfigInterfaceMockRecorder, hostManager *MockHostManagerMockRecorder) {
-				gomock.InOrder(
-					dynconfig.Get().Return(&config.DynconfigData{
-						Scheduler: &managerv2.Scheduler{
-							SeedPeers: []*managerv2.SeedPeer{},
-						},
-					}, nil).Times(1),
-					dynconfig.Register(gomock.Any()).Return().Times(1),
-					dynconfig.GetResolveSeedPeerAddrs().Return([]resolver.Address{}, nil).Times(1),
-					dynconfig.Register(gomock.Any()).Return().Times(1),
-					dynconfig.GetResolveSeedPeerAddrs().Return([]resolver.Address{}, nil).Times(1),
-					dynconfig.Register(gomock.Any()).Return().Times(1),
-				)
-			},
-			expect: func(t *testing.T, err error) {
-				assert := assert.New(t)
-				assert.NoError(err)
-			},
-		},
-	}
-
-	for _, tc := range tests {
-		t.Run(tc.name, func(t *testing.T) {
-			ctl := gomock.NewController(t)
-			defer ctl.Finish()
-			dynconfig := configmocks.NewMockDynconfigInterface(ctl)
-			hostManager := NewMockHostManager(ctl)
-			tc.mock(dynconfig.EXPECT(), hostManager.EXPECT())
-
-			_, err := newSeedPeerClient(dynconfig, hostManager, grpc.WithTransportCredentials(insecure.NewCredentials()))
-			tc.expect(t, err)
-		})
-	}
-}
-
-func TestSeedPeerClinet_Addrs(t *testing.T) {
-	tests := []struct {
-		name   string
-		sc     *seedPeerClient
-		expect func(t *testing.T, addrs []string)
-	}{
-		{
-			name: "return the addresses of seed peers",
-			sc: &seedPeerClient{
-				data: &config.DynconfigData{
-					Scheduler: &managerv2.Scheduler{
-						SeedPeers: []*managerv2.SeedPeer{
-							{
-								Ip:   "0.0.0.0",
-								Port: 8080,
-							},
-							{
-								Ip:   "127.0.0.1",
-								Port: 5000,
-							},
-						},
-					},
-				},
-			},
-			expect: func(t *testing.T, addrs []string) {
-				assert := assert.New(t)
-				assert.Equal("0.0.0.0:8080", addrs[0])
-				assert.Equal("127.0.0.1:5000", addrs[1])
-			},
-		},
-	}
-
-	for _, tc := range tests {
-		t.Run(tc.name, func(t *testing.T) {
-			addrs := tc.sc.Addrs()
-			tc.expect(t, addrs)
-		})
-	}
-}
-
-func TestSeedPeerClient_OnNotify(t *testing.T) {
-	tests := []struct {
-		name string
-		data *config.DynconfigData
-		mock func(dynconfig *configmocks.MockDynconfigInterfaceMockRecorder, hostManager *MockHostManagerMockRecorder)
-	}{
-		{
-			name: "notify client with same seed peers",
-			data: &config.DynconfigData{
-				Scheduler: &managerv2.Scheduler{
-					SeedPeers: []*managerv2.SeedPeer{
-						{
-							Id:       1,
-							Hostname: "foo",
-							Ip:       "0.0.0.0",
-							Port:     8080,
-						},
-					},
-				},
-			},
-			mock: func(dynconfig *configmocks.MockDynconfigInterfaceMockRecorder, hostManager *MockHostManagerMockRecorder) {
-				gomock.InOrder(
-					dynconfig.Get().Return(&config.DynconfigData{
-						Scheduler: &managerv2.Scheduler{
-							SeedPeers: []*managerv2.SeedPeer{
-								{
-									Id:       1,
-									Hostname: "foo",
-									Ip:       "0.0.0.0",
-									Port:     8080,
-								},
-							},
-						},
-					}, nil).Times(1),
-					dynconfig.Register(gomock.Any()).Return().Times(1),
-					dynconfig.GetResolveSeedPeerAddrs().Return([]resolver.Address{}, nil).Times(1),
-					dynconfig.Register(gomock.Any()).Return().Times(1),
-					dynconfig.GetResolveSeedPeerAddrs().Return([]resolver.Address{}, nil).Times(1),
-					hostManager.Load(gomock.Any()).Return(nil, false).Times(1),
-					hostManager.Store(gomock.Any()).Return().Times(1),
-					dynconfig.Register(gomock.Any()).Return().Times(1),
-				)
-			},
-		},
-		{
-			name: "notify client with different seed peers",
-			data: &config.DynconfigData{
-				Scheduler: &managerv2.Scheduler{
-					SeedPeers: []*managerv2.SeedPeer{
-						{
-							Id:       1,
-							Hostname: "foo",
-							Ip:       "0.0.0.0",
-						},
-					},
-				},
-			},
-			mock: func(dynconfig *configmocks.MockDynconfigInterfaceMockRecorder, hostManager *MockHostManagerMockRecorder) {
-				mockHost := NewHost(
-					mockRawHost.ID, mockRawHost.IP, mockRawHost.Hostname,
-					mockRawHost.Port, mockRawHost.DownloadPort, mockRawHost.Type)
-				gomock.InOrder(
-					dynconfig.Get().Return(&config.DynconfigData{
-						Scheduler: &managerv2.Scheduler{
-							SeedPeers: []*managerv2.SeedPeer{
-								{
-									Id:       1,
-									Hostname: "foo",
-									Ip:       "127.0.0.1",
-								},
-							},
-						},
-					}, nil).Times(1),
-					dynconfig.Register(gomock.Any()).Return().Times(1),
-					dynconfig.GetResolveSeedPeerAddrs().Return([]resolver.Address{}, nil).Times(1),
-					dynconfig.Register(gomock.Any()).Return().Times(1),
-					dynconfig.GetResolveSeedPeerAddrs().Return([]resolver.Address{}, nil).Times(1),
-					hostManager.Load(gomock.Any()).Return(nil, false).Times(1),
-					hostManager.Store(gomock.Any()).Return().Times(1),
-					dynconfig.Register(gomock.Any()).Return().Times(1),
-					hostManager.Load(gomock.Any()).Return(mockHost, true).Times(1),
-				)
-			},
-		},
-		{
-			name: "notify client with different seed peers and load host failed",
-			data: &config.DynconfigData{
-				Scheduler: &managerv2.Scheduler{
-					SeedPeers: []*managerv2.SeedPeer{
-						{
-							Id:       1,
-							Hostname: "foo",
-							Ip:       "0.0.0.0",
-						},
-					},
-				},
-			},
-			mock: func(dynconfig *configmocks.MockDynconfigInterfaceMockRecorder, hostManager *MockHostManagerMockRecorder) {
-				gomock.InOrder(
-					dynconfig.Get().Return(&config.DynconfigData{
-						Scheduler: &managerv2.Scheduler{
-							SeedPeers: []*managerv2.SeedPeer{
-								{
-									Id:       1,
-									Hostname: "foo",
-									Ip:       "127.0.0.1",
-								},
-							},
-						},
-					}, nil).Times(1),
-					dynconfig.Register(gomock.Any()).Return().Times(1),
-					dynconfig.GetResolveSeedPeerAddrs().Return([]resolver.Address{}, nil).Times(1),
-					dynconfig.Register(gomock.Any()).Return().Times(1),
-					dynconfig.GetResolveSeedPeerAddrs().Return([]resolver.Address{}, nil).Times(1),
-					hostManager.Load(gomock.Any()).Return(nil, false).Times(1),
-					hostManager.Store(gomock.Any()).Return().Times(1),
-					dynconfig.Register(gomock.Any()).Return().Times(1),
-					hostManager.Load(gomock.Any()).Return(nil, false).Times(1),
-					hostManager.Store(gomock.Any()).Return().Times(1),
-				)
-			},
-		},
-		{
-			name: "seed peer list is deep equal",
-			data: &config.DynconfigData{
-				Scheduler: &managerv2.Scheduler{
-					SeedPeers: []*managerv2.SeedPeer{
-						{
-							Id: 1,
-							Ip: "127.0.0.1",
-						},
-					},
-				},
-			},
-			mock: func(dynconfig *configmocks.MockDynconfigInterfaceMockRecorder, hostManager *MockHostManagerMockRecorder) {
-				gomock.InOrder(
-					dynconfig.Get().Return(&config.DynconfigData{
-						Scheduler: &managerv2.Scheduler{
-							SeedPeers: []*managerv2.SeedPeer{
-								{
-									Id: 1,
-									Ip: "127.0.0.1",
-								},
-							},
-						},
-					}, nil).Times(1),
-					dynconfig.Register(gomock.Any()).Return().Times(1),
-					dynconfig.GetResolveSeedPeerAddrs().Return([]resolver.Address{}, nil).Times(1),
-					dynconfig.Register(gomock.Any()).Return().Times(1),
-					dynconfig.GetResolveSeedPeerAddrs().Return([]resolver.Address{}, nil).Times(1),
-					hostManager.Load(gomock.Any()).Return(nil, false).Times(1),
-					hostManager.Store(gomock.Any()).Return().Times(1),
-					dynconfig.Register(gomock.Any()).Return().Times(1),
-				)
-			},
-		},
-	}
-
-	for _, tc := range tests {
-		t.Run(tc.name, func(t *testing.T) {
-			ctl := gomock.NewController(t)
-			defer ctl.Finish()
-			dynconfig := configmocks.NewMockDynconfigInterface(ctl)
-			hostManager := NewMockHostManager(ctl)
-			tc.mock(dynconfig.EXPECT(), hostManager.EXPECT())
-
-			client, err := newSeedPeerClient(dynconfig, hostManager, grpc.WithTransportCredentials(insecure.NewCredentials()))
-			if err != nil {
-				t.Fatal(err)
-			}
-			client.OnNotify(tc.data)
-		})
-	}
-}
-
-func TestSeedPeerClient_seedPeersToNetAddrs(t *testing.T) {
-	tests := []struct {
-		name      string
-		seedPeers []*managerv2.SeedPeer
-		expect    func(t *testing.T, netAddrs []dfnet.NetAddr)
-	}{
-		{
-			name: "seed peers covert to netAddr",
-			seedPeers: []*managerv2.SeedPeer{
-				{
-					Id:           1,
-					Type:         pkgtypes.HostTypeSuperSeedName,
-					Hostname:     mockRawSeedHost.Hostname,
-					Ip:           mockRawSeedHost.IP,
-					Port:         mockRawSeedHost.Port,
-					DownloadPort: mockRawSeedHost.DownloadPort,
-					Idc:          &mockRawSeedHost.Network.IDC,
-					Location:     &mockRawSeedHost.Network.Location,
-				},
-			},
-			expect: func(t *testing.T, netAddrs []dfnet.NetAddr) {
-				assert := assert.New(t)
-				assert.Equal(netAddrs[0].Type, dfnet.TCP)
-				assert.Equal(netAddrs[0].Addr, fmt.Sprintf("%s:%d", mockRawSeedHost.IP, mockRawSeedHost.Port))
-			},
-		},
-		{
-			name:      "seed peers is empty",
-			seedPeers: []*managerv2.SeedPeer{},
-			expect: func(t *testing.T, netAddrs []dfnet.NetAddr) {
-				assert := assert.New(t)
-				assert.Equal(len(netAddrs), 0)
-			},
-		},
-	}
-
-	for _, tc := range tests {
-		t.Run(tc.name, func(t *testing.T) {
-			tc.expect(t, seedPeersToNetAddrs(tc.seedPeers))
-		})
-	}
-}
diff --git a/scheduler/resource/standard/seed_peer_test.go b/scheduler/resource/standard/seed_peer_test.go
index 46bcab16..c82aba27 100644
--- a/scheduler/resource/standard/seed_peer_test.go
+++ b/scheduler/resource/standard/seed_peer_test.go
@@ -18,7 +18,6 @@ package standard
 
 import (
 	"context"
-	"errors"
 	"reflect"
 	"testing"
 
@@ -50,9 +49,8 @@ func TestSeedPeer_newSeedPeer(t *testing.T) {
 			defer ctl.Finish()
 			hostManager := NewMockHostManager(ctl)
 			peerManager := NewMockPeerManager(ctl)
-			client := NewMockSeedPeerClient(ctl)
 
-			tc.expect(t, newSeedPeer(client, peerManager, hostManager))
+			tc.expect(t, newSeedPeer(peerManager, hostManager))
 		})
 	}
 }
@@ -60,17 +58,13 @@ func TestSeedPeer_newSeedPeer(t *testing.T) {
 func TestSeedPeer_TriggerDownloadTask(t *testing.T) {
 	tests := []struct {
 		name   string
-		mock   func(mc *MockSeedPeerClientMockRecorder)
 		expect func(t *testing.T, err error)
 	}{
 		{
 			name: "trigger download task failed",
-			mock: func(mc *MockSeedPeerClientMockRecorder) {
-				mc.DownloadTask(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errors.New("foo")).Times(1)
-			},
 			expect: func(t *testing.T, err error) {
 				assert := assert.New(t)
-				assert.EqualError(err, "foo")
+				assert.EqualError(err, "no seed peer available")
 			},
 		},
 	}
@@ -81,10 +75,8 @@ func TestSeedPeer_TriggerDownloadTask(t *testing.T) {
 			defer ctl.Finish()
 			hostManager := NewMockHostManager(ctl)
 			peerManager := NewMockPeerManager(ctl)
-			client := NewMockSeedPeerClient(ctl)
-			tc.mock(client.EXPECT())
 
-			seedPeer := newSeedPeer(client, peerManager, hostManager)
+			seedPeer := newSeedPeer(peerManager, hostManager)
 			tc.expect(t, seedPeer.TriggerDownloadTask(context.Background(), mockTaskID, &dfdaemonv2.DownloadTaskRequest{}))
 		})
 	}
@@ -93,17 +85,13 @@ func TestSeedPeer_TriggerDownloadTask(t *testing.T) {
 func TestSeedPeer_TriggerTask(t *testing.T) {
 	tests := []struct {
 		name   string
-		mock   func(mc *MockSeedPeerClientMockRecorder)
 		expect func(t *testing.T, peer *Peer, result *schedulerv1.PeerResult, err error)
 	}{
 		{
 			name: "start obtain seed stream failed",
-			mock: func(mc *MockSeedPeerClientMockRecorder) {
-				mc.ObtainSeeds(gomock.Any(), gomock.Any()).Return(nil, errors.New("foo")).Times(1)
-			},
 			expect: func(t *testing.T, peer *Peer, result *schedulerv1.PeerResult, err error) {
 				assert := assert.New(t)
-				assert.EqualError(err, "foo")
+				assert.EqualError(err, "no seed peer available")
 			},
 		},
 	}
@@ -114,10 +102,8 @@ func TestSeedPeer_TriggerTask(t *testing.T) {
 			defer ctl.Finish()
 			hostManager := NewMockHostManager(ctl)
 			peerManager := NewMockPeerManager(ctl)
-			client := NewMockSeedPeerClient(ctl)
-			tc.mock(client.EXPECT())
 
-			seedPeer := newSeedPeer(client, peerManager, hostManager)
+			seedPeer := newSeedPeer(peerManager, hostManager)
 			mockTask := NewTask(mockTaskID, mockTaskURL, mockTaskTag, mockTaskApplication, commonv2.TaskType_STANDARD, mockTaskFilteredQueryParams, mockTaskHeader, mockTaskBackToSourceLimit, WithDigest(mockTaskDigest))
 			peer, result, err := seedPeer.TriggerTask(context.Background(), nil, mockTask)
 			tc.expect(t, peer, result, err)