Skip to content

Commit 4abb179

Browse files
authored
feat: support preheating by v2 grpc protocol (#3201)
Signed-off-by: Gaius <gaius.qi@gmail.com>
1 parent 501a7be commit 4abb179

File tree

8 files changed

+132
-46
lines changed

8 files changed

+132
-46
lines changed

internal/job/types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type PreheatRequest struct {
2424
Headers map[string]string `json:"headers" validate:"omitempty"`
2525
Application string `json:"application" validate:"omitempty"`
2626
Priority int32 `json:"priority" validate:"omitempty"`
27+
PieceLength uint32 `json:"pieceLength" validate:"omitempty"`
2728
}
2829

2930
type PreheatResponse struct {

manager/job/preheat.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ func (p *preheat) CreatePreheat(ctx context.Context, schedulers []models.Schedul
109109
URL: json.URL,
110110
Tag: json.Tag,
111111
FilteredQueryParams: json.FilteredQueryParams,
112+
PieceLength: json.PieceLength,
112113
Headers: json.Headers,
113114
},
114115
}
@@ -304,6 +305,7 @@ func (p *preheat) parseLayers(manifests []distribution.Manifest, args types.Preh
304305
URL: image.blobsURL(v.Digest.String()),
305306
Tag: args.Tag,
306307
FilteredQueryParams: args.FilteredQueryParams,
308+
PieceLength: args.PieceLength,
307309
Headers: nethttp.HeaderToMap(header),
308310
}
309311

manager/service/job.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ func (s *service) CreatePreheatJob(ctx context.Context, json types.CreatePreheat
3737
return nil, err
3838
}
3939

40+
if json.Args.PieceLength == 0 {
41+
json.Args.PieceLength = types.DefaultPreheatJobPieceLength
42+
}
43+
4044
groupJobState, err := s.job.CreatePreheat(ctx, candidateSchedulers, json.Args)
4145
if err != nil {
4246
return nil, err

manager/service/preheat.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ func (s *service) CreateV1Preheat(ctx context.Context, json types.CreateV1Prehea
5151
Type: json.Type,
5252
URL: json.URL,
5353
FilteredQueryParams: json.FilteredQueryParams,
54+
PieceLength: types.DefaultPreheatJobPieceLength,
5455
Headers: json.Headers,
5556
},
5657
})

manager/types/job.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@
1616

1717
package types
1818

19+
const (
20+
// DefaultPreheatJobPieceLength is the default piece length for preheating.
21+
DefaultPreheatJobPieceLength = 4 * 1024 * 1024
22+
)
23+
1924
type CreateJobRequest struct {
2025
BIO string `json:"bio" binding:"omitempty"`
2126
Type string `json:"type" binding:"required"`
@@ -65,6 +70,9 @@ type PreheatArgs struct {
6570
// FilteredQueryParams is the filtered query params for preheating.
6671
FilteredQueryParams string `json:"filteredQueryParams" binding:"omitempty"`
6772

73+
// PieceLength is the piece length for preheating.
74+
PieceLength uint32 `json:"pieceLength" binding:"omitempty"`
75+
6876
// Headers is the http headers for authentication.
6977
Headers map[string]string `json:"headers" binding:"omitempty"`
7078

scheduler/config/dynconfig.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -419,9 +419,9 @@ func (mc *managerClient) Get() (any, error) {
419419
Ip: mc.config.Server.AdvertiseIP.String(),
420420
})
421421
if err != nil {
422-
if s, ok := status.FromError(err); ok {
422+
if st, ok := status.FromError(err); ok {
423423
// TODO Compatible with old version manager.
424-
if slices.Contains([]codes.Code{codes.Unimplemented, codes.NotFound}, s.Code()) {
424+
if slices.Contains([]codes.Code{codes.Unimplemented, codes.NotFound}, st.Code()) {
425425
return DynconfigData{
426426
Scheduler: getSchedulerResp,
427427
Applications: nil,

scheduler/job/job.go

Lines changed: 91 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,23 @@ import (
2222
"context"
2323
"errors"
2424
"fmt"
25+
"io"
2526
"strings"
2627
"time"
2728

2829
"github.com/RichardKnop/machinery/v1"
29-
"github.com/go-http-utils/headers"
3030
"github.com/go-playground/validator/v10"
31+
"google.golang.org/grpc/codes"
32+
"google.golang.org/grpc/status"
3133

3234
cdnsystemv1 "d7y.io/api/v2/pkg/apis/cdnsystem/v1"
3335
commonv1 "d7y.io/api/v2/pkg/apis/common/v1"
36+
commonv2 "d7y.io/api/v2/pkg/apis/common/v2"
37+
dfdaemonv2 "d7y.io/api/v2/pkg/apis/dfdaemon/v2"
3438

3539
logger "d7y.io/dragonfly/v2/internal/dflog"
3640
internaljob "d7y.io/dragonfly/v2/internal/job"
3741
"d7y.io/dragonfly/v2/pkg/idgen"
38-
"d7y.io/dragonfly/v2/pkg/net/http"
3942
"d7y.io/dragonfly/v2/scheduler/config"
4043
"d7y.io/dragonfly/v2/scheduler/resource"
4144
)
@@ -148,8 +151,9 @@ func (j *job) Serve() {
148151
}()
149152
}
150153

151-
// preheat is a job to preheat.
152-
func (j *job) preheat(ctx context.Context, req string) error {
154+
// preheat is a job to preheat, it is not supported to preheat
155+
// with range requests.
156+
func (j *job) preheat(ctx context.Context, data string) error {
153157
ctx, cancel := context.WithTimeout(ctx, preheatTimeout)
154158
defer cancel()
155159

@@ -163,63 +167,120 @@ func (j *job) preheat(ctx context.Context, req string) error {
163167
return fmt.Errorf("cluster %d scheduler %s has no available seed peer", j.config.Manager.SchedulerClusterID, j.config.Server.AdvertiseIP)
164168
}
165169

166-
preheat := &internaljob.PreheatRequest{}
167-
if err := internaljob.UnmarshalRequest(req, preheat); err != nil {
168-
logger.Errorf("unmarshal request err: %s, request body: %s", err.Error(), req)
170+
req := &internaljob.PreheatRequest{}
171+
if err := internaljob.UnmarshalRequest(data, req); err != nil {
172+
logger.Errorf("unmarshal request err: %s, request body: %s", err.Error(), data)
169173
return err
170174
}
171175

172-
if err := validator.New().Struct(preheat); err != nil {
173-
logger.Errorf("preheat %s validate failed: %s", preheat.URL, err.Error())
176+
if err := validator.New().Struct(req); err != nil {
177+
logger.Errorf("preheat %s validate failed: %s", req.URL, err.Error())
174178
return err
175179
}
176180

177-
urlMeta := &commonv1.UrlMeta{
178-
Digest: preheat.Digest,
179-
Tag: preheat.Tag,
180-
Filter: preheat.FilteredQueryParams,
181-
Header: preheat.Headers,
182-
Application: preheat.Application,
183-
Priority: commonv1.Priority(preheat.Priority),
184-
}
185-
if preheat.Headers != nil {
186-
if r, ok := preheat.Headers[headers.Range]; ok {
187-
// Range in dragonfly is without "bytes=".
188-
urlMeta.Range = strings.TrimPrefix(r, http.RangePrefix)
181+
// Preheat by v2 grpc protocol. If seed peer does not support
182+
// v2 protocol, preheat by v1 grpc protocol.
183+
if err := j.preheatV2(ctx, req); err != nil {
184+
logger.Errorf("preheat %s failed: %s", req.URL, err.Error())
185+
186+
if st, ok := status.FromError(err); ok {
187+
if st.Code() == codes.Unimplemented {
188+
if err := j.preheatV1(ctx, req); err != nil {
189+
return err
190+
}
191+
192+
return nil
193+
}
189194
}
195+
196+
return err
197+
}
198+
199+
return nil
200+
}
201+
202+
// preheatV1 preheats job by v1 grpc protocol.
203+
func (j *job) preheatV1(ctx context.Context, req *internaljob.PreheatRequest) error {
204+
urlMeta := &commonv1.UrlMeta{
205+
Digest: req.Digest,
206+
Tag: req.Tag,
207+
Filter: req.FilteredQueryParams,
208+
Header: req.Headers,
209+
Application: req.Application,
210+
Priority: commonv1.Priority(req.Priority),
190211
}
191212

192213
// Trigger seed peer download seeds.
193-
taskID := idgen.TaskIDV1(preheat.URL, urlMeta)
194-
log := logger.WithTask(taskID, preheat.URL)
195-
log.Infof("preheat %s tag: %s, range: %s, filtered query params: %s, digest: %s",
196-
preheat.URL, urlMeta.Tag, urlMeta.Range, urlMeta.Filter, urlMeta.Digest)
197-
log.Debugf("preheat %s headers: %#v", preheat.URL, urlMeta.Header)
214+
taskID := idgen.TaskIDV1(req.URL, urlMeta)
215+
log := logger.WithTask(taskID, req.URL)
216+
log.Infof("preheat(v1) %s tag: %s, filtered query params: %s, digest: %s, headers: %#v",
217+
req.URL, urlMeta.Tag, urlMeta.Filter, urlMeta.Digest, urlMeta.Header)
198218

199219
stream, err := j.resource.SeedPeer().Client().ObtainSeeds(ctx, &cdnsystemv1.SeedRequest{
200220
TaskId: taskID,
201-
Url: preheat.URL,
221+
Url: req.URL,
202222
UrlMeta: urlMeta,
203223
})
204224
if err != nil {
205-
log.Errorf("preheat %s failed: %s", preheat.URL, err.Error())
225+
log.Errorf("preheat(v1) %s failed: %s", req.URL, err.Error())
206226
return err
207227
}
208228

209229
for {
210230
piece, err := stream.Recv()
211231
if err != nil {
212-
log.Errorf("preheat %s recive piece failed: %s", preheat.URL, err.Error())
232+
log.Errorf("preheat(v1) %s recive piece failed: %s", req.URL, err.Error())
213233
return err
214234
}
215235

216236
if piece.Done == true {
217-
log.Infof("preheat %s succeeded", preheat.URL)
237+
log.Infof("preheat(v1) %s succeeded", req.URL)
218238
return nil
219239
}
220240
}
221241
}
222242

243+
// preheatV2 preheats job by v2 grpc protocol.
244+
func (j *job) preheatV2(ctx context.Context, req *internaljob.PreheatRequest) error {
245+
filteredQueryParams := strings.Split(req.FilteredQueryParams, idgen.FilteredQueryParamsSeparator)
246+
taskID := idgen.TaskIDV2(req.URL, req.Digest, req.Tag, req.Application, int32(req.PieceLength), filteredQueryParams)
247+
248+
log := logger.WithTask(taskID, req.URL)
249+
log.Infof("preheat(v2) %s tag: %s, filtered query params: %s, digest: %s, headers: %#v",
250+
req.URL, req.Tag, req.FilteredQueryParams, req.Digest, req.Headers)
251+
252+
stream, err := j.resource.SeedPeer().Client().DownloadTask(ctx, taskID, &dfdaemonv2.DownloadTaskRequest{
253+
Download: &commonv2.Download{
254+
Url: req.URL,
255+
Digest: &req.Digest,
256+
Type: commonv2.TaskType_DFDAEMON,
257+
Tag: &req.Tag,
258+
Application: &req.Application,
259+
Priority: commonv2.Priority(req.Priority),
260+
FilteredQueryParams: filteredQueryParams,
261+
RequestHeader: req.Headers,
262+
PieceLength: uint32(req.PieceLength),
263+
}})
264+
if err != nil {
265+
logger.Errorf("preheat(v2) %s failed: %s", req.URL, err.Error())
266+
return err
267+
}
268+
269+
// Wait for the download task to complete.
270+
for {
271+
_, err := stream.Recv()
272+
if err != nil {
273+
if err == io.EOF {
274+
log.Infof("preheat(v2) %s succeeded", req.URL)
275+
return nil
276+
}
277+
278+
log.Errorf("preheat(v2) %s recive piece failed: %s", req.URL, err.Error())
279+
return err
280+
}
281+
}
282+
}
283+
223284
// syncPeers is a job to sync peers.
224285
func (j *job) syncPeers() (string, error) {
225286
var hosts []*resource.Host

scheduler/service/service_v2.go

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -819,7 +819,7 @@ func (v *V2) SyncProbes(stream schedulerv2.Scheduler_SyncProbesServer) error {
819819
// handleRegisterPeerRequest handles RegisterPeerRequest of AnnouncePeerRequest.
820820
func (v *V2) handleRegisterPeerRequest(ctx context.Context, stream schedulerv2.Scheduler_AnnouncePeerServer, hostID, taskID, peerID string, req *schedulerv2.RegisterPeerRequest) error {
821821
// Handle resource included host, task, and peer.
822-
_, task, peer, err := v.handleResource(ctx, stream, hostID, taskID, peerID, req.GetDownload())
822+
host, task, peer, err := v.handleResource(ctx, stream, hostID, taskID, peerID, req.GetDownload())
823823
if err != nil {
824824
return err
825825
}
@@ -836,7 +836,7 @@ func (v *V2) handleRegisterPeerRequest(ctx context.Context, stream schedulerv2.S
836836
// If scheduler trigger seed peer download back-to-source,
837837
// the needBackToSource flag should be true.
838838
case download.GetNeedBackToSource():
839-
peer.Log.Infof("peer need back to source")
839+
peer.Log.Info("peer need back to source")
840840
peer.NeedBackToSource.Store(true)
841841
// If task is pending, failed, leave, or succeeded and has no available peer,
842842
// scheduler trigger seed peer download back-to-source.
@@ -845,18 +845,27 @@ func (v *V2) handleRegisterPeerRequest(ctx context.Context, stream schedulerv2.S
845845
task.FSM.Is(resource.TaskStateLeave) ||
846846
task.FSM.Is(resource.TaskStateSucceeded) &&
847847
!task.HasAvailablePeer(blocklist):
848-
// If trigger the seed peer download back-to-source,
849-
// the need back-to-source flag should be true.
850-
download.NeedBackToSource = true
851-
852-
// Output path should be empty, prevent the seed peer
853-
// copy file to output path.
854-
download.OutputPath = nil
855-
if err := v.downloadTaskBySeedPeer(ctx, taskID, download, peer); err != nil {
856-
// Collect RegisterPeerFailureCount metrics.
857-
metrics.RegisterPeerFailureCount.WithLabelValues(priority.String(), peer.Task.Type.String(),
858-
peer.Task.Tag, peer.Task.Application, peer.Host.Type.Name()).Inc()
859-
return err
848+
849+
// If HostType is normal, trigger seed peer download back-to-source.
850+
if host.Type == types.HostTypeNormal {
851+
// If trigger the seed peer download back-to-source,
852+
// the need back-to-source flag should be true.
853+
download.NeedBackToSource = true
854+
855+
// Output path should be empty, prevent the seed peer
856+
// copy file to output path.
857+
download.OutputPath = nil
858+
if err := v.downloadTaskBySeedPeer(ctx, taskID, download, peer); err != nil {
859+
// Collect RegisterPeerFailureCount metrics.
860+
metrics.RegisterPeerFailureCount.WithLabelValues(priority.String(), peer.Task.Type.String(),
861+
peer.Task.Tag, peer.Task.Application, peer.Host.Type.Name()).Inc()
862+
return err
863+
}
864+
} else {
865+
// If HostType is not normal, peer is seed peer, and
866+
// trigger seed peer download back-to-source directly.
867+
peer.Log.Info("peer need back to source")
868+
peer.NeedBackToSource.Store(true)
860869
}
861870
}
862871

0 commit comments

Comments
 (0)