/* * Copyright 2020 The Dragonfly Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cdn import ( "context" "fmt" "io" "io/ioutil" "net/http" "sync" "d7y.io/dragonfly/v2/internal/dfcodes" "d7y.io/dragonfly/v2/internal/dferrors" logger "d7y.io/dragonfly/v2/internal/dflog" "d7y.io/dragonfly/v2/pkg/rpc/cdnsystem" "d7y.io/dragonfly/v2/pkg/rpc/cdnsystem/client" "d7y.io/dragonfly/v2/scheduler/config" "d7y.io/dragonfly/v2/scheduler/supervisor" "github.com/pkg/errors" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/trace" ) var ( ErrCDNRegisterFail = errors.New("cdn task register failed") ErrCDNDownloadFail = errors.New("cdn task download failed") ErrCDNUnknown = errors.New("cdn obtain seed encounter unknown err") ErrCDNInvokeFail = errors.New("invoke cdn interface failed") ErrInitCDNPeerFail = errors.New("init cdn peer failed") ) var tracer trace.Tracer func init() { tracer = otel.Tracer("scheduler-cdn") } type manager struct { client RefreshableCDNClient peerManager supervisor.PeerMgr hostManager supervisor.HostMgr lock sync.RWMutex } func NewManager(cdnClient RefreshableCDNClient, peerManager supervisor.PeerMgr, hostManager supervisor.HostMgr) (supervisor.CDNMgr, error) { mgr := &manager{ client: cdnClient, peerManager: peerManager, hostManager: hostManager, } return mgr, nil } func (cm *manager) StartSeedTask(ctx context.Context, task *supervisor.Task) (*supervisor.Peer, error) { logger.Infof("start seed task %s", task.TaskID) defer logger.Infof("finish seed task %s", task.TaskID) var seedSpan trace.Span ctx, seedSpan = tracer.Start(ctx, config.SpanTriggerCDN) defer seedSpan.End() seedRequest := &cdnsystem.SeedRequest{ TaskId: task.TaskID, Url: task.URL, UrlMeta: task.URLMeta, } seedSpan.SetAttributes(config.AttributeCDNSeedRequest.String(seedRequest.String())) if cm.client == nil { err := ErrCDNRegisterFail seedSpan.RecordError(err) seedSpan.SetAttributes(config.AttributePeerDownloadSuccess.Bool(false)) return nil, err } stream, err := cm.client.ObtainSeeds(trace.ContextWithSpan(context.Background(), seedSpan), seedRequest) if err != nil { seedSpan.RecordError(err) seedSpan.SetAttributes(config.AttributePeerDownloadSuccess.Bool(false)) if cdnErr, ok := err.(*dferrors.DfError); ok { logger.Errorf("failed to obtain cdn seed: %v", cdnErr) switch cdnErr.Code { case dfcodes.CdnTaskRegistryFail: return nil, errors.Wrap(ErrCDNRegisterFail, "obtain seeds") case dfcodes.CdnTaskDownloadFail: return nil, errors.Wrapf(ErrCDNDownloadFail, "obtain seeds") default: return nil, errors.Wrapf(ErrCDNUnknown, "obtain seeds") } } return nil, errors.Wrapf(ErrCDNInvokeFail, "obtain seeds from cdn: %v", err) } return cm.receivePiece(ctx, task, stream) } func (cm *manager) receivePiece(ctx context.Context, task *supervisor.Task, stream *client.PieceSeedStream) (*supervisor.Peer, error) { span := trace.SpanFromContext(ctx) var initialized bool var cdnPeer *supervisor.Peer for { piece, err := stream.Recv() if err == io.EOF { if task.GetStatus() == supervisor.TaskStatusSuccess { span.SetAttributes(config.AttributePeerDownloadSuccess.Bool(true)) return cdnPeer, nil } return cdnPeer, errors.Errorf("cdn stream receive EOF but task status is %s", task.GetStatus()) } if err != nil { span.RecordError(err) span.SetAttributes(config.AttributePeerDownloadSuccess.Bool(false)) if recvErr, ok := err.(*dferrors.DfError); ok { span.RecordError(recvErr) switch recvErr.Code { case dfcodes.CdnTaskRegistryFail: return cdnPeer, errors.Wrapf(ErrCDNRegisterFail, "receive piece") case dfcodes.CdnTaskDownloadFail: return cdnPeer, errors.Wrapf(ErrCDNDownloadFail, "receive piece") default: return cdnPeer, errors.Wrapf(ErrCDNUnknown, "recive piece") } } return cdnPeer, errors.Wrapf(ErrCDNInvokeFail, "receive piece from cdn: %v", err) } if piece != nil { span.AddEvent(config.EventPieceReceived, trace.WithAttributes(config.AttributePieceReceived.String(piece.String()))) if !initialized { cdnPeer, err = cm.initCdnPeer(ctx, task, piece) task.SetStatus(supervisor.TaskStatusSeeding) initialized = true } if err != nil || cdnPeer == nil { return cdnPeer, err } cdnPeer.Touch() if piece.Done { task.PieceTotal = piece.TotalPieceCount task.ContentLength = piece.ContentLength task.SetStatus(supervisor.TaskStatusSuccess) cdnPeer.SetStatus(supervisor.PeerStatusSuccess) if task.ContentLength <= supervisor.TinyFileSize { content, er := cm.DownloadTinyFileContent(ctx, task, cdnPeer.Host) if er == nil && len(content) == int(task.ContentLength) { task.DirectPiece = content } } span.SetAttributes(config.AttributePeerDownloadSuccess.Bool(true)) span.SetAttributes(config.AttributeContentLength.Int64(task.ContentLength)) return cdnPeer, nil } cdnPeer.UpdateProgress(piece.PieceInfo.PieceNum+1, 0) task.AddPiece(piece.PieceInfo) } } } func (cm *manager) initCdnPeer(ctx context.Context, task *supervisor.Task, ps *cdnsystem.PieceSeed) (*supervisor.Peer, error) { span := trace.SpanFromContext(ctx) span.AddEvent(config.EventCreateCDNPeer) var ok bool var cdnHost *supervisor.PeerHost cdnPeer, ok := cm.peerManager.Get(ps.PeerId) if !ok { if cdnHost, ok = cm.hostManager.Get(ps.HostUuid); !ok { if cdnHost, ok = cm.client.GetCDNHost(ps.HostUuid); !ok { logger.Errorf("cannot find cdn host %s", ps.HostUuid) return nil, errors.Wrapf(ErrInitCDNPeerFail, "cannot find host %s", ps.HostUuid) } cm.hostManager.Add(cdnHost) } cdnPeer = supervisor.NewPeer(ps.PeerId, task, cdnHost) } cdnPeer.SetStatus(supervisor.PeerStatusRunning) cm.peerManager.Add(cdnPeer) return cdnPeer, nil } func (cm *manager) DownloadTinyFileContent(ctx context.Context, task *supervisor.Task, cdnHost *supervisor.PeerHost) ([]byte, error) { span := trace.SpanFromContext(ctx) // http://host:port/download/{taskId 前3位}/{taskId}?peerId={peerId}; url := fmt.Sprintf("http://%s:%d/download/%s/%s?peerId=scheduler", cdnHost.IP, cdnHost.DownloadPort, task.TaskID[:3], task.TaskID) span.AddEvent(config.EventDownloadTinyFile, trace.WithAttributes(config.AttributeDownloadFileURL.String(url))) response, err := http.Get(url) if err != nil { return nil, err } defer response.Body.Close() content, err := ioutil.ReadAll(response.Body) if err != nil { return nil, err } return content, nil }